brew vendor-gems: commit updates.
This commit is contained in:
parent
8528eff604
commit
9329a4c454
16
Library/Homebrew/vendor/bundle/bundler/setup.rb
vendored
16
Library/Homebrew/vendor/bundle/bundler/setup.rb
vendored
@ -13,9 +13,9 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-6.0.3.4
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ast-2.4.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/bindata-2.4.8/lib"
|
||||
$:.unshift "#{path}/"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/byebug-11.1.3"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/byebug-11.1.3"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/byebug-11.1.3/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/json-2.3.1"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/json-2.3.1"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/json-2.3.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/docile-1.3.2/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/simplecov-html-0.12.3/lib"
|
||||
@ -28,12 +28,12 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/highline-2.0.3/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/commander-4.5.2/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/connection_pool-2.2.3/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/diff-lcs-1.4.4/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/unf_ext-0.0.7.7"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/unf_ext-0.0.7.7"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf_ext-0.0.7.7/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unf-0.1.4/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/domain_name-0.5.20190701/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/elftools-1.1.3/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/hpricot-0.8.6"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/hpricot-0.8.6"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/hpricot-0.8.6/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/http-cookie-1.0.3/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mime-types-data-3.2020.1104/lib"
|
||||
@ -41,7 +41,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mime-types-3.3.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/net-http-digest_auth-1.4.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/net-http-persistent-4.0.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/mini_portile2-2.4.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/nokogiri-1.10.10"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/nokogiri-1.10.10"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/nokogiri-1.10.10/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ntlm-http-0.1.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/webrobots-0.1.2/lib"
|
||||
@ -57,7 +57,8 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/parlour-4.0.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/patchelf-1.3.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.5.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/pry-0.13.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-19/2.6.0/rdiscount-2.2.0.2"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rack-2.2.3/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/extensions/universal-darwin-20/2.6.0/rdiscount-2.2.0.2"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rdiscount-2.2.0.2/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/regexp_parser-2.0.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rexml-3.2.4/lib"
|
||||
@ -69,7 +70,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-mocks-3.10.0/li
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-3.10.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-its-1.3.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-retry-0.6.2/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.6111-universal-darwin-19/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-static-0.5.6111-universal-darwin-20/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sorbet-0.5.6111/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-sorbet-1.7.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rspec-wait-0.0.9/lib"
|
||||
@ -78,6 +79,7 @@ $:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-progressbar-1.10
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/unicode-display_width-1.7.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-1.3.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-performance-1.9.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rails-2.8.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-rspec-2.0.0/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rubocop-sorbet-0.5.1/lib"
|
||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/ruby-macho-2.5.0/lib"
|
||||
|
||||
141
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack.rb
vendored
Normal file
141
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack.rb
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
|
||||
#
|
||||
# Rack is freely distributable under the terms of an MIT-style license.
|
||||
# See MIT-LICENSE or https://opensource.org/licenses/MIT.
|
||||
|
||||
# The Rack main module, serving as a namespace for all core Rack
|
||||
# modules and classes.
|
||||
#
|
||||
# All modules meant for use in your application are <tt>autoload</tt>ed here,
|
||||
# so it should be enough just to <tt>require 'rack'</tt> in your code.
|
||||
|
||||
require_relative 'rack/version'
|
||||
|
||||
module Rack
|
||||
HTTP_HOST = 'HTTP_HOST'
|
||||
HTTP_PORT = 'HTTP_PORT'
|
||||
HTTP_VERSION = 'HTTP_VERSION'
|
||||
HTTPS = 'HTTPS'
|
||||
PATH_INFO = 'PATH_INFO'
|
||||
REQUEST_METHOD = 'REQUEST_METHOD'
|
||||
REQUEST_PATH = 'REQUEST_PATH'
|
||||
SCRIPT_NAME = 'SCRIPT_NAME'
|
||||
QUERY_STRING = 'QUERY_STRING'
|
||||
SERVER_PROTOCOL = 'SERVER_PROTOCOL'
|
||||
SERVER_NAME = 'SERVER_NAME'
|
||||
SERVER_PORT = 'SERVER_PORT'
|
||||
CACHE_CONTROL = 'Cache-Control'
|
||||
EXPIRES = 'Expires'
|
||||
CONTENT_LENGTH = 'Content-Length'
|
||||
CONTENT_TYPE = 'Content-Type'
|
||||
SET_COOKIE = 'Set-Cookie'
|
||||
TRANSFER_ENCODING = 'Transfer-Encoding'
|
||||
HTTP_COOKIE = 'HTTP_COOKIE'
|
||||
ETAG = 'ETag'
|
||||
|
||||
# HTTP method verbs
|
||||
GET = 'GET'
|
||||
POST = 'POST'
|
||||
PUT = 'PUT'
|
||||
PATCH = 'PATCH'
|
||||
DELETE = 'DELETE'
|
||||
HEAD = 'HEAD'
|
||||
OPTIONS = 'OPTIONS'
|
||||
LINK = 'LINK'
|
||||
UNLINK = 'UNLINK'
|
||||
TRACE = 'TRACE'
|
||||
|
||||
# Rack environment variables
|
||||
RACK_VERSION = 'rack.version'
|
||||
RACK_TEMPFILES = 'rack.tempfiles'
|
||||
RACK_ERRORS = 'rack.errors'
|
||||
RACK_LOGGER = 'rack.logger'
|
||||
RACK_INPUT = 'rack.input'
|
||||
RACK_SESSION = 'rack.session'
|
||||
RACK_SESSION_OPTIONS = 'rack.session.options'
|
||||
RACK_SHOWSTATUS_DETAIL = 'rack.showstatus.detail'
|
||||
RACK_MULTITHREAD = 'rack.multithread'
|
||||
RACK_MULTIPROCESS = 'rack.multiprocess'
|
||||
RACK_RUNONCE = 'rack.run_once'
|
||||
RACK_URL_SCHEME = 'rack.url_scheme'
|
||||
RACK_HIJACK = 'rack.hijack'
|
||||
RACK_IS_HIJACK = 'rack.hijack?'
|
||||
RACK_HIJACK_IO = 'rack.hijack_io'
|
||||
RACK_RECURSIVE_INCLUDE = 'rack.recursive.include'
|
||||
RACK_MULTIPART_BUFFER_SIZE = 'rack.multipart.buffer_size'
|
||||
RACK_MULTIPART_TEMPFILE_FACTORY = 'rack.multipart.tempfile_factory'
|
||||
RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'
|
||||
RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'
|
||||
RACK_REQUEST_FORM_VARS = 'rack.request.form_vars'
|
||||
RACK_REQUEST_COOKIE_HASH = 'rack.request.cookie_hash'
|
||||
RACK_REQUEST_COOKIE_STRING = 'rack.request.cookie_string'
|
||||
RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'
|
||||
RACK_REQUEST_QUERY_STRING = 'rack.request.query_string'
|
||||
RACK_METHODOVERRIDE_ORIGINAL_METHOD = 'rack.methodoverride.original_method'
|
||||
RACK_SESSION_UNPACKED_COOKIE_DATA = 'rack.session.unpacked_cookie_data'
|
||||
|
||||
autoload :Builder, "rack/builder"
|
||||
autoload :BodyProxy, "rack/body_proxy"
|
||||
autoload :Cascade, "rack/cascade"
|
||||
autoload :Chunked, "rack/chunked"
|
||||
autoload :CommonLogger, "rack/common_logger"
|
||||
autoload :ConditionalGet, "rack/conditional_get"
|
||||
autoload :Config, "rack/config"
|
||||
autoload :ContentLength, "rack/content_length"
|
||||
autoload :ContentType, "rack/content_type"
|
||||
autoload :ETag, "rack/etag"
|
||||
autoload :Events, "rack/events"
|
||||
autoload :File, "rack/file"
|
||||
autoload :Files, "rack/files"
|
||||
autoload :Deflater, "rack/deflater"
|
||||
autoload :Directory, "rack/directory"
|
||||
autoload :ForwardRequest, "rack/recursive"
|
||||
autoload :Handler, "rack/handler"
|
||||
autoload :Head, "rack/head"
|
||||
autoload :Lint, "rack/lint"
|
||||
autoload :Lock, "rack/lock"
|
||||
autoload :Logger, "rack/logger"
|
||||
autoload :MediaType, "rack/media_type"
|
||||
autoload :MethodOverride, "rack/method_override"
|
||||
autoload :Mime, "rack/mime"
|
||||
autoload :NullLogger, "rack/null_logger"
|
||||
autoload :Recursive, "rack/recursive"
|
||||
autoload :Reloader, "rack/reloader"
|
||||
autoload :RewindableInput, "rack/rewindable_input"
|
||||
autoload :Runtime, "rack/runtime"
|
||||
autoload :Sendfile, "rack/sendfile"
|
||||
autoload :Server, "rack/server"
|
||||
autoload :ShowExceptions, "rack/show_exceptions"
|
||||
autoload :ShowStatus, "rack/show_status"
|
||||
autoload :Static, "rack/static"
|
||||
autoload :TempfileReaper, "rack/tempfile_reaper"
|
||||
autoload :URLMap, "rack/urlmap"
|
||||
autoload :Utils, "rack/utils"
|
||||
autoload :Multipart, "rack/multipart"
|
||||
|
||||
autoload :MockRequest, "rack/mock"
|
||||
autoload :MockResponse, "rack/mock"
|
||||
|
||||
autoload :Request, "rack/request"
|
||||
autoload :Response, "rack/response"
|
||||
|
||||
module Auth
|
||||
autoload :Basic, "rack/auth/basic"
|
||||
autoload :AbstractRequest, "rack/auth/abstract/request"
|
||||
autoload :AbstractHandler, "rack/auth/abstract/handler"
|
||||
module Digest
|
||||
autoload :MD5, "rack/auth/digest/md5"
|
||||
autoload :Nonce, "rack/auth/digest/nonce"
|
||||
autoload :Params, "rack/auth/digest/params"
|
||||
autoload :Request, "rack/auth/digest/request"
|
||||
end
|
||||
end
|
||||
|
||||
module Session
|
||||
autoload :Cookie, "rack/session/cookie"
|
||||
autoload :Pool, "rack/session/pool"
|
||||
autoload :Memcache, "rack/session/memcache"
|
||||
end
|
||||
end
|
||||
39
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/abstract/handler.rb
vendored
Normal file
39
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/abstract/handler.rb
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
module Auth
|
||||
# Rack::Auth::AbstractHandler implements common authentication functionality.
|
||||
#
|
||||
# +realm+ should be set for all handlers.
|
||||
|
||||
class AbstractHandler
|
||||
|
||||
attr_accessor :realm
|
||||
|
||||
def initialize(app, realm = nil, &authenticator)
|
||||
@app, @realm, @authenticator = app, realm, authenticator
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def unauthorized(www_authenticate = challenge)
|
||||
return [ 401,
|
||||
{ CONTENT_TYPE => 'text/plain',
|
||||
CONTENT_LENGTH => '0',
|
||||
'WWW-Authenticate' => www_authenticate.to_s },
|
||||
[]
|
||||
]
|
||||
end
|
||||
|
||||
def bad_request
|
||||
return [ 400,
|
||||
{ CONTENT_TYPE => 'text/plain',
|
||||
CONTENT_LENGTH => '0' },
|
||||
[]
|
||||
]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
47
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/abstract/request.rb
vendored
Normal file
47
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/abstract/request.rb
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
module Auth
|
||||
class AbstractRequest
|
||||
|
||||
def initialize(env)
|
||||
@env = env
|
||||
end
|
||||
|
||||
def request
|
||||
@request ||= Request.new(@env)
|
||||
end
|
||||
|
||||
def provided?
|
||||
!authorization_key.nil? && valid?
|
||||
end
|
||||
|
||||
def valid?
|
||||
!@env[authorization_key].nil?
|
||||
end
|
||||
|
||||
def parts
|
||||
@parts ||= @env[authorization_key].split(' ', 2)
|
||||
end
|
||||
|
||||
def scheme
|
||||
@scheme ||= parts.first && parts.first.downcase
|
||||
end
|
||||
|
||||
def params
|
||||
@params ||= parts.last
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
|
||||
|
||||
def authorization_key
|
||||
@authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
61
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/basic.rb
vendored
Normal file
61
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/basic.rb
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'abstract/handler'
|
||||
require_relative 'abstract/request'
|
||||
require 'base64'
|
||||
|
||||
module Rack
|
||||
module Auth
|
||||
# Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.
|
||||
#
|
||||
# Initialize with the Rack application that you want protecting,
|
||||
# and a block that checks if a username and password pair are valid.
|
||||
#
|
||||
# See also: <tt>example/protectedlobster.rb</tt>
|
||||
|
||||
class Basic < AbstractHandler
|
||||
|
||||
def call(env)
|
||||
auth = Basic::Request.new(env)
|
||||
|
||||
return unauthorized unless auth.provided?
|
||||
|
||||
return bad_request unless auth.basic?
|
||||
|
||||
if valid?(auth)
|
||||
env['REMOTE_USER'] = auth.username
|
||||
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
unauthorized
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def challenge
|
||||
'Basic realm="%s"' % realm
|
||||
end
|
||||
|
||||
def valid?(auth)
|
||||
@authenticator.call(*auth.credentials)
|
||||
end
|
||||
|
||||
class Request < Auth::AbstractRequest
|
||||
def basic?
|
||||
"basic" == scheme && credentials.length == 2
|
||||
end
|
||||
|
||||
def credentials
|
||||
@credentials ||= Base64.decode64(params).split(':', 2)
|
||||
end
|
||||
|
||||
def username
|
||||
credentials.first
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
131
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/md5.rb
vendored
Normal file
131
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/md5.rb
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../abstract/handler'
|
||||
require_relative 'request'
|
||||
require_relative 'params'
|
||||
require_relative 'nonce'
|
||||
require 'digest/md5'
|
||||
|
||||
module Rack
|
||||
module Auth
|
||||
module Digest
|
||||
# Rack::Auth::Digest::MD5 implements the MD5 algorithm version of
|
||||
# HTTP Digest Authentication, as per RFC 2617.
|
||||
#
|
||||
# Initialize with the [Rack] application that you want protecting,
|
||||
# and a block that looks up a plaintext password for a given username.
|
||||
#
|
||||
# +opaque+ needs to be set to a constant base64/hexadecimal string.
|
||||
#
|
||||
class MD5 < AbstractHandler
|
||||
|
||||
attr_accessor :opaque
|
||||
|
||||
attr_writer :passwords_hashed
|
||||
|
||||
def initialize(app, realm = nil, opaque = nil, &authenticator)
|
||||
@passwords_hashed = nil
|
||||
if opaque.nil? and realm.respond_to? :values_at
|
||||
realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed
|
||||
end
|
||||
super(app, realm, &authenticator)
|
||||
@opaque = opaque
|
||||
end
|
||||
|
||||
def passwords_hashed?
|
||||
!!@passwords_hashed
|
||||
end
|
||||
|
||||
def call(env)
|
||||
auth = Request.new(env)
|
||||
|
||||
unless auth.provided?
|
||||
return unauthorized
|
||||
end
|
||||
|
||||
if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth)
|
||||
return bad_request
|
||||
end
|
||||
|
||||
if valid?(auth)
|
||||
if auth.nonce.stale?
|
||||
return unauthorized(challenge(stale: true))
|
||||
else
|
||||
env['REMOTE_USER'] = auth.username
|
||||
|
||||
return @app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
unauthorized
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
QOP = 'auth'
|
||||
|
||||
def params(hash = {})
|
||||
Params.new do |params|
|
||||
params['realm'] = realm
|
||||
params['nonce'] = Nonce.new.to_s
|
||||
params['opaque'] = H(opaque)
|
||||
params['qop'] = QOP
|
||||
|
||||
hash.each { |k, v| params[k] = v }
|
||||
end
|
||||
end
|
||||
|
||||
def challenge(hash = {})
|
||||
"Digest #{params(hash)}"
|
||||
end
|
||||
|
||||
def valid?(auth)
|
||||
valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth)
|
||||
end
|
||||
|
||||
def valid_qop?(auth)
|
||||
QOP == auth.qop
|
||||
end
|
||||
|
||||
def valid_opaque?(auth)
|
||||
H(opaque) == auth.opaque
|
||||
end
|
||||
|
||||
def valid_nonce?(auth)
|
||||
auth.nonce.valid?
|
||||
end
|
||||
|
||||
def valid_digest?(auth)
|
||||
pw = @authenticator.call(auth.username)
|
||||
pw && Rack::Utils.secure_compare(digest(auth, pw), auth.response)
|
||||
end
|
||||
|
||||
def md5(data)
|
||||
::Digest::MD5.hexdigest(data)
|
||||
end
|
||||
|
||||
alias :H :md5
|
||||
|
||||
def KD(secret, data)
|
||||
H "#{secret}:#{data}"
|
||||
end
|
||||
|
||||
def A1(auth, password)
|
||||
"#{auth.username}:#{auth.realm}:#{password}"
|
||||
end
|
||||
|
||||
def A2(auth)
|
||||
"#{auth.method}:#{auth.uri}"
|
||||
end
|
||||
|
||||
def digest(auth, password)
|
||||
password_hash = passwords_hashed? ? password : H(A1(auth, password))
|
||||
|
||||
KD password_hash, "#{auth.nonce}:#{auth.nc}:#{auth.cnonce}:#{QOP}:#{H A2(auth)}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
54
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/nonce.rb
vendored
Normal file
54
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/nonce.rb
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'digest/md5'
|
||||
require 'base64'
|
||||
|
||||
module Rack
|
||||
module Auth
|
||||
module Digest
|
||||
# Rack::Auth::Digest::Nonce is the default nonce generator for the
|
||||
# Rack::Auth::Digest::MD5 authentication handler.
|
||||
#
|
||||
# +private_key+ needs to set to a constant string.
|
||||
#
|
||||
# +time_limit+ can be optionally set to an integer (number of seconds),
|
||||
# to limit the validity of the generated nonces.
|
||||
|
||||
class Nonce
|
||||
|
||||
class << self
|
||||
attr_accessor :private_key, :time_limit
|
||||
end
|
||||
|
||||
def self.parse(string)
|
||||
new(*Base64.decode64(string).split(' ', 2))
|
||||
end
|
||||
|
||||
def initialize(timestamp = Time.now, given_digest = nil)
|
||||
@timestamp, @given_digest = timestamp.to_i, given_digest
|
||||
end
|
||||
|
||||
def to_s
|
||||
Base64.encode64("#{@timestamp} #{digest}").strip
|
||||
end
|
||||
|
||||
def digest
|
||||
::Digest::MD5.hexdigest("#{@timestamp}:#{self.class.private_key}")
|
||||
end
|
||||
|
||||
def valid?
|
||||
digest == @given_digest
|
||||
end
|
||||
|
||||
def stale?
|
||||
!self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
|
||||
end
|
||||
|
||||
def fresh?
|
||||
!stale?
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
54
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/params.rb
vendored
Normal file
54
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/params.rb
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
module Auth
|
||||
module Digest
|
||||
class Params < Hash
|
||||
|
||||
def self.parse(str)
|
||||
Params[*split_header_value(str).map do |param|
|
||||
k, v = param.split('=', 2)
|
||||
[k, dequote(v)]
|
||||
end.flatten]
|
||||
end
|
||||
|
||||
def self.dequote(str) # From WEBrick::HTTPUtils
|
||||
ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup
|
||||
ret.gsub!(/\\(.)/, "\\1")
|
||||
ret
|
||||
end
|
||||
|
||||
def self.split_header_value(str)
|
||||
str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n)
|
||||
end
|
||||
|
||||
def initialize
|
||||
super()
|
||||
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
def [](k)
|
||||
super k.to_s
|
||||
end
|
||||
|
||||
def []=(k, v)
|
||||
super k.to_s, v.to_s
|
||||
end
|
||||
|
||||
UNQUOTED = ['nc', 'stale']
|
||||
|
||||
def to_s
|
||||
map do |k, v|
|
||||
"#{k}=#{(UNQUOTED.include?(k) ? v.to_s : quote(v))}"
|
||||
end.join(', ')
|
||||
end
|
||||
|
||||
def quote(str) # From WEBrick::HTTPUtils
|
||||
'"' + str.gsub(/[\\\"]/o, "\\\1") + '"'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/request.rb
vendored
Normal file
43
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/auth/digest/request.rb
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../abstract/request'
|
||||
require_relative 'params'
|
||||
require_relative 'nonce'
|
||||
|
||||
module Rack
|
||||
module Auth
|
||||
module Digest
|
||||
class Request < Auth::AbstractRequest
|
||||
def method
|
||||
@env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] || @env[REQUEST_METHOD]
|
||||
end
|
||||
|
||||
def digest?
|
||||
"digest" == scheme
|
||||
end
|
||||
|
||||
def correct_uri?
|
||||
request.fullpath == uri
|
||||
end
|
||||
|
||||
def nonce
|
||||
@nonce ||= Nonce.parse(params['nonce'])
|
||||
end
|
||||
|
||||
def params
|
||||
@params ||= Params.parse(parts.last)
|
||||
end
|
||||
|
||||
def respond_to?(sym, *)
|
||||
super or params.has_key? sym.to_s
|
||||
end
|
||||
|
||||
def method_missing(sym, *args)
|
||||
return super unless params.has_key?(key = sym.to_s)
|
||||
return params[key] if args.size == 0
|
||||
raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
45
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/body_proxy.rb
vendored
Normal file
45
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/body_proxy.rb
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Proxy for response bodies allowing calling a block when
|
||||
# the response body is closed (after the response has been fully
|
||||
# sent to the client).
|
||||
class BodyProxy
|
||||
# Set the response body to wrap, and the block to call when the
|
||||
# response has been fully sent.
|
||||
def initialize(body, &block)
|
||||
@body = body
|
||||
@block = block
|
||||
@closed = false
|
||||
end
|
||||
|
||||
# Return whether the wrapped body responds to the method.
|
||||
def respond_to_missing?(method_name, include_all = false)
|
||||
super or @body.respond_to?(method_name, include_all)
|
||||
end
|
||||
|
||||
# If not already closed, close the wrapped body and
|
||||
# then call the block the proxy was initialized with.
|
||||
def close
|
||||
return if @closed
|
||||
@closed = true
|
||||
begin
|
||||
@body.close if @body.respond_to? :close
|
||||
ensure
|
||||
@block.call
|
||||
end
|
||||
end
|
||||
|
||||
# Whether the proxy is closed. The proxy starts as not closed,
|
||||
# and becomes closed on the first call to close.
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
# Delegate missing methods to the wrapped body.
|
||||
def method_missing(method_name, *args, &block)
|
||||
@body.__send__(method_name, *args, &block)
|
||||
end
|
||||
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
||||
end
|
||||
end
|
||||
257
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/builder.rb
vendored
Normal file
257
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/builder.rb
vendored
Normal file
@ -0,0 +1,257 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Rack::Builder implements a small DSL to iteratively construct Rack
|
||||
# applications.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# require 'rack/lobster'
|
||||
# app = Rack::Builder.new do
|
||||
# use Rack::CommonLogger
|
||||
# use Rack::ShowExceptions
|
||||
# map "/lobster" do
|
||||
# use Rack::Lint
|
||||
# run Rack::Lobster.new
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# run app
|
||||
#
|
||||
# Or
|
||||
#
|
||||
# app = Rack::Builder.app do
|
||||
# use Rack::CommonLogger
|
||||
# run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
|
||||
# end
|
||||
#
|
||||
# run app
|
||||
#
|
||||
# +use+ adds middleware to the stack, +run+ dispatches to an application.
|
||||
# You can use +map+ to construct a Rack::URLMap in a convenient way.
|
||||
|
||||
class Builder
|
||||
|
||||
# https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
|
||||
UTF_8_BOM = '\xef\xbb\xbf'
|
||||
|
||||
# Parse the given config file to get a Rack application.
|
||||
#
|
||||
# If the config file ends in +.ru+, it is treated as a
|
||||
# rackup file and the contents will be treated as if
|
||||
# specified inside a Rack::Builder block, using the given
|
||||
# options.
|
||||
#
|
||||
# If the config file does not end in +.ru+, it is
|
||||
# required and Rack will use the basename of the file
|
||||
# to guess which constant will be the Rack application to run.
|
||||
# The options given will be ignored in this case.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# Rack::Builder.parse_file('config.ru')
|
||||
# # Rack application built using Rack::Builder.new
|
||||
#
|
||||
# Rack::Builder.parse_file('app.rb')
|
||||
# # requires app.rb, which can be anywhere in Ruby's
|
||||
# # load path. After requiring, assumes App constant
|
||||
# # contains Rack application
|
||||
#
|
||||
# Rack::Builder.parse_file('./my_app.rb')
|
||||
# # requires ./my_app.rb, which should be in the
|
||||
# # process's current directory. After requiring,
|
||||
# # assumes MyApp constant contains Rack application
|
||||
def self.parse_file(config, opts = Server::Options.new)
|
||||
if config.end_with?('.ru')
|
||||
return self.load_file(config, opts)
|
||||
else
|
||||
require config
|
||||
app = Object.const_get(::File.basename(config, '.rb').split('_').map(&:capitalize).join(''))
|
||||
return app, {}
|
||||
end
|
||||
end
|
||||
|
||||
# Load the given file as a rackup file, treating the
|
||||
# contents as if specified inside a Rack::Builder block.
|
||||
#
|
||||
# Treats the first comment at the beginning of a line
|
||||
# that starts with a backslash as options similar to
|
||||
# options passed on a rackup command line.
|
||||
#
|
||||
# Ignores content in the file after +__END__+, so that
|
||||
# use of +__END__+ will not result in a syntax error.
|
||||
#
|
||||
# Example config.ru file:
|
||||
#
|
||||
# $ cat config.ru
|
||||
#
|
||||
# #\ -p 9393
|
||||
#
|
||||
# use Rack::ContentLength
|
||||
# require './app.rb'
|
||||
# run App
|
||||
def self.load_file(path, opts = Server::Options.new)
|
||||
options = {}
|
||||
|
||||
cfgfile = ::File.read(path)
|
||||
cfgfile.slice!(/\A#{UTF_8_BOM}/) if cfgfile.encoding == Encoding::UTF_8
|
||||
|
||||
if cfgfile[/^#\\(.*)/] && opts
|
||||
warn "Parsing options from the first comment line is deprecated!"
|
||||
options = opts.parse! $1.split(/\s+/)
|
||||
end
|
||||
|
||||
cfgfile.sub!(/^__END__\n.*\Z/m, '')
|
||||
app = new_from_string cfgfile, path
|
||||
|
||||
return app, options
|
||||
end
|
||||
|
||||
# Evaluate the given +builder_script+ string in the context of
|
||||
# a Rack::Builder block, returning a Rack application.
|
||||
def self.new_from_string(builder_script, file = "(rackup)")
|
||||
# We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
|
||||
# We cannot use instance_eval(String) as that would resolve constants differently.
|
||||
binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
|
||||
eval builder_script, binding, file
|
||||
builder.to_app
|
||||
end
|
||||
|
||||
# Initialize a new Rack::Builder instance. +default_app+ specifies the
|
||||
# default application if +run+ is not called later. If a block
|
||||
# is given, it is evaluted in the context of the instance.
|
||||
def initialize(default_app = nil, &block)
|
||||
@use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
|
||||
instance_eval(&block) if block_given?
|
||||
end
|
||||
|
||||
# Create a new Rack::Builder instance and return the Rack application
|
||||
# generated from it.
|
||||
def self.app(default_app = nil, &block)
|
||||
self.new(default_app, &block).to_app
|
||||
end
|
||||
|
||||
# Specifies middleware to use in a stack.
|
||||
#
|
||||
# class Middleware
|
||||
# def initialize(app)
|
||||
# @app = app
|
||||
# end
|
||||
#
|
||||
# def call(env)
|
||||
# env["rack.some_header"] = "setting an example"
|
||||
# @app.call(env)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# use Middleware
|
||||
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
||||
#
|
||||
# All requests through to this application will first be processed by the middleware class.
|
||||
# The +call+ method in this example sets an additional environment key which then can be
|
||||
# referenced in the application if required.
|
||||
def use(middleware, *args, &block)
|
||||
if @map
|
||||
mapping, @map = @map, nil
|
||||
@use << proc { |app| generate_map(app, mapping) }
|
||||
end
|
||||
@use << proc { |app| middleware.new(app, *args, &block) }
|
||||
end
|
||||
ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
|
||||
|
||||
# Takes an argument that is an object that responds to #call and returns a Rack response.
|
||||
# The simplest form of this is a lambda object:
|
||||
#
|
||||
# run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
|
||||
#
|
||||
# However this could also be a class:
|
||||
#
|
||||
# class Heartbeat
|
||||
# def self.call(env)
|
||||
# [200, { "Content-Type" => "text/plain" }, ["OK"]]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# run Heartbeat
|
||||
def run(app)
|
||||
@run = app
|
||||
end
|
||||
|
||||
# Takes a lambda or block that is used to warm-up the application. This block is called
|
||||
# before the Rack application is returned by to_app.
|
||||
#
|
||||
# warmup do |app|
|
||||
# client = Rack::MockRequest.new(app)
|
||||
# client.get('/')
|
||||
# end
|
||||
#
|
||||
# use SomeMiddleware
|
||||
# run MyApp
|
||||
def warmup(prc = nil, &block)
|
||||
@warmup = prc || block
|
||||
end
|
||||
|
||||
# Creates a route within the application. Routes under the mapped path will be sent to
|
||||
# the Rack application specified by run inside the block. Other requests will be sent to the
|
||||
# default application specified by run outside the block.
|
||||
#
|
||||
# Rack::Builder.app do
|
||||
# map '/heartbeat' do
|
||||
# run Heartbeat
|
||||
# end
|
||||
# run App
|
||||
# end
|
||||
#
|
||||
# The +use+ method can also be used inside the block to specify middleware to run under a specific path:
|
||||
#
|
||||
# Rack::Builder.app do
|
||||
# map '/heartbeat' do
|
||||
# use Middleware
|
||||
# run Heartbeat
|
||||
# end
|
||||
# run App
|
||||
# end
|
||||
#
|
||||
# This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+.
|
||||
#
|
||||
# Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement
|
||||
# outside the block.
|
||||
def map(path, &block)
|
||||
@map ||= {}
|
||||
@map[path] = block
|
||||
end
|
||||
|
||||
# Freeze the app (set using run) and all middleware instances when building the application
|
||||
# in to_app.
|
||||
def freeze_app
|
||||
@freeze_app = true
|
||||
end
|
||||
|
||||
# Return the Rack application generated by this instance.
|
||||
def to_app
|
||||
app = @map ? generate_map(@run, @map) : @run
|
||||
fail "missing run or map statement" unless app
|
||||
app.freeze if @freeze_app
|
||||
app = @use.reverse.inject(app) { |a, e| e[a].tap { |x| x.freeze if @freeze_app } }
|
||||
@warmup.call(app) if @warmup
|
||||
app
|
||||
end
|
||||
|
||||
# Call the Rack application generated by this builder instance. Note that
|
||||
# this rebuilds the Rack application and runs the warmup code (if any)
|
||||
# every time it is called, so it should not be used if performance is important.
|
||||
def call(env)
|
||||
to_app.call(env)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Generate a URLMap instance by generating new Rack applications for each
|
||||
# map block in this instance.
|
||||
def generate_map(default_app, mapping)
|
||||
mapped = default_app ? { '/' => default_app } : {}
|
||||
mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
|
||||
URLMap.new(mapped)
|
||||
end
|
||||
end
|
||||
end
|
||||
68
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/cascade.rb
vendored
Normal file
68
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/cascade.rb
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Rack::Cascade tries a request on several apps, and returns the
|
||||
# first response that is not 404 or 405 (or in a list of configured
|
||||
# status codes). If all applications tried return one of the configured
|
||||
# status codes, return the last response.
|
||||
|
||||
class Cascade
|
||||
# deprecated, no longer used
|
||||
NotFound = [404, { CONTENT_TYPE => "text/plain" }, []]
|
||||
|
||||
# An array of applications to try in order.
|
||||
attr_reader :apps
|
||||
|
||||
# Set the apps to send requests to, and what statuses result in
|
||||
# cascading. Arguments:
|
||||
#
|
||||
# apps: An enumerable of rack applications.
|
||||
# cascade_for: The statuses to use cascading for. If a response is received
|
||||
# from an app, the next app is tried.
|
||||
def initialize(apps, cascade_for = [404, 405])
|
||||
@apps = []
|
||||
apps.each { |app| add app }
|
||||
|
||||
@cascade_for = {}
|
||||
[*cascade_for].each { |status| @cascade_for[status] = true }
|
||||
end
|
||||
|
||||
# Call each app in order. If the responses uses a status that requires
|
||||
# cascading, try the next app. If all responses require cascading,
|
||||
# return the response from the last app.
|
||||
def call(env)
|
||||
return [404, { CONTENT_TYPE => "text/plain" }, []] if @apps.empty?
|
||||
result = nil
|
||||
last_body = nil
|
||||
|
||||
@apps.each do |app|
|
||||
# The SPEC says that the body must be closed after it has been iterated
|
||||
# by the server, or if it is replaced by a middleware action. Cascade
|
||||
# replaces the body each time a cascade happens. It is assumed that nil
|
||||
# does not respond to close, otherwise the previous application body
|
||||
# will be closed. The final application body will not be closed, as it
|
||||
# will be passed to the server as a result.
|
||||
last_body.close if last_body.respond_to? :close
|
||||
|
||||
result = app.call(env)
|
||||
return result unless @cascade_for.include?(result[0].to_i)
|
||||
last_body = result[2]
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Append an app to the list of apps to cascade. This app will
|
||||
# be tried last.
|
||||
def add(app)
|
||||
@apps << app
|
||||
end
|
||||
|
||||
# Whether the given app is one of the apps to cascade to.
|
||||
def include?(app)
|
||||
@apps.include?(app)
|
||||
end
|
||||
|
||||
alias_method :<<, :add
|
||||
end
|
||||
end
|
||||
117
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/chunked.rb
vendored
Normal file
117
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/chunked.rb
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
|
||||
# Middleware that applies chunked transfer encoding to response bodies
|
||||
# when the response does not include a Content-Length header.
|
||||
#
|
||||
# This supports the Trailer response header to allow the use of trailing
|
||||
# headers in the chunked encoding. However, using this requires you manually
|
||||
# specify a response body that supports a +trailers+ method. Example:
|
||||
#
|
||||
# [200, { 'Trailer' => 'Expires'}, ["Hello", "World"]]
|
||||
# # error raised
|
||||
#
|
||||
# body = ["Hello", "World"]
|
||||
# def body.trailers
|
||||
# { 'Expires' => Time.now.to_s }
|
||||
# end
|
||||
# [200, { 'Trailer' => 'Expires'}, body]
|
||||
# # No exception raised
|
||||
class Chunked
|
||||
include Rack::Utils
|
||||
|
||||
# A body wrapper that emits chunked responses.
|
||||
class Body
|
||||
TERM = "\r\n"
|
||||
TAIL = "0#{TERM}"
|
||||
|
||||
# Store the response body to be chunked.
|
||||
def initialize(body)
|
||||
@body = body
|
||||
end
|
||||
|
||||
# For each element yielded by the response body, yield
|
||||
# the element in chunked encoding.
|
||||
def each(&block)
|
||||
term = TERM
|
||||
@body.each do |chunk|
|
||||
size = chunk.bytesize
|
||||
next if size == 0
|
||||
|
||||
yield [size.to_s(16), term, chunk.b, term].join
|
||||
end
|
||||
yield TAIL
|
||||
yield_trailers(&block)
|
||||
yield term
|
||||
end
|
||||
|
||||
# Close the response body if the response body supports it.
|
||||
def close
|
||||
@body.close if @body.respond_to?(:close)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Do nothing as this class does not support trailer headers.
|
||||
def yield_trailers
|
||||
end
|
||||
end
|
||||
|
||||
# A body wrapper that emits chunked responses and also supports
|
||||
# sending Trailer headers. Note that the response body provided to
|
||||
# initialize must have a +trailers+ method that returns a hash
|
||||
# of trailer headers, and the rack response itself should have a
|
||||
# Trailer header listing the headers that the +trailers+ method
|
||||
# will return.
|
||||
class TrailerBody < Body
|
||||
private
|
||||
|
||||
# Yield strings for each trailer header.
|
||||
def yield_trailers
|
||||
@body.trailers.each_pair do |k, v|
|
||||
yield "#{k}: #{v}\r\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
# Whether the HTTP version supports chunked encoding (HTTP 1.1 does).
|
||||
def chunkable_version?(ver)
|
||||
case ver
|
||||
# pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have
|
||||
# a version (nor response headers)
|
||||
when 'HTTP/1.0', nil, 'HTTP/0.9'
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# If the rack app returns a response that should have a body,
|
||||
# but does not have Content-Length or Transfer-Encoding headers,
|
||||
# modify the response to use chunked Transfer-Encoding.
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
headers = HeaderHash[headers]
|
||||
|
||||
if chunkable_version?(env[SERVER_PROTOCOL]) &&
|
||||
!STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
|
||||
!headers[CONTENT_LENGTH] &&
|
||||
!headers[TRANSFER_ENCODING]
|
||||
|
||||
headers[TRANSFER_ENCODING] = 'chunked'
|
||||
if headers['Trailer']
|
||||
body = TrailerBody.new(body)
|
||||
else
|
||||
body = Body.new(body)
|
||||
end
|
||||
end
|
||||
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
end
|
||||
80
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/common_logger.rb
vendored
Normal file
80
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/common_logger.rb
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Rack::CommonLogger forwards every request to the given +app+, and
|
||||
# logs a line in the
|
||||
# {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
|
||||
# to the configured logger.
|
||||
class CommonLogger
|
||||
# Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
|
||||
#
|
||||
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
|
||||
#
|
||||
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
|
||||
#
|
||||
# The actual format is slightly different than the above due to the
|
||||
# separation of SCRIPT_NAME and PATH_INFO, and because the elapsed
|
||||
# time in seconds is included at the end.
|
||||
FORMAT = %{%s - %s [%s] "%s %s%s%s %s" %d %s %0.4f\n}
|
||||
|
||||
# +logger+ can be any object that supports the +write+ or +<<+ methods,
|
||||
# which includes the standard library Logger. These methods are called
|
||||
# with a single string argument, the log message.
|
||||
# If +logger+ is nil, CommonLogger will fall back <tt>env['rack.errors']</tt>.
|
||||
def initialize(app, logger = nil)
|
||||
@app = app
|
||||
@logger = logger
|
||||
end
|
||||
|
||||
# Log all requests in common_log format after a response has been
|
||||
# returned. Note that if the app raises an exception, the request
|
||||
# will not be logged, so if exception handling middleware are used,
|
||||
# they should be loaded after this middleware. Additionally, because
|
||||
# the logging happens after the request body has been fully sent, any
|
||||
# exceptions raised during the sending of the response body will
|
||||
# cause the request not to be logged.
|
||||
def call(env)
|
||||
began_at = Utils.clock_time
|
||||
status, headers, body = @app.call(env)
|
||||
headers = Utils::HeaderHash[headers]
|
||||
body = BodyProxy.new(body) { log(env, status, headers, began_at) }
|
||||
[status, headers, body]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Log the request to the configured logger.
|
||||
def log(env, status, header, began_at)
|
||||
length = extract_content_length(header)
|
||||
|
||||
msg = FORMAT % [
|
||||
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
||||
env["REMOTE_USER"] || "-",
|
||||
Time.now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
||||
env[REQUEST_METHOD],
|
||||
env[SCRIPT_NAME],
|
||||
env[PATH_INFO],
|
||||
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
||||
env[SERVER_PROTOCOL],
|
||||
status.to_s[0..3],
|
||||
length,
|
||||
Utils.clock_time - began_at ]
|
||||
|
||||
logger = @logger || env[RACK_ERRORS]
|
||||
# Standard library logger doesn't support write but it supports << which actually
|
||||
# calls to write on the log device without formatting
|
||||
if logger.respond_to?(:write)
|
||||
logger.write(msg)
|
||||
else
|
||||
logger << msg
|
||||
end
|
||||
end
|
||||
|
||||
# Attempt to determine the content length for the response to
|
||||
# include it in the logged data.
|
||||
def extract_content_length(headers)
|
||||
value = headers[CONTENT_LENGTH]
|
||||
!value || value.to_s == '0' ? '-' : value
|
||||
end
|
||||
end
|
||||
end
|
||||
83
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/conditional_get.rb
vendored
Normal file
83
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/conditional_get.rb
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
|
||||
# Middleware that enables conditional GET using If-None-Match and
|
||||
# If-Modified-Since. The application should set either or both of the
|
||||
# Last-Modified or Etag response headers according to RFC 2616. When
|
||||
# either of the conditions is met, the response body is set to be zero
|
||||
# length and the response status is set to 304 Not Modified.
|
||||
#
|
||||
# Applications that defer response body generation until the body's each
|
||||
# message is received will avoid response body generation completely when
|
||||
# a conditional GET matches.
|
||||
#
|
||||
# Adapted from Michael Klishin's Merb implementation:
|
||||
# https://github.com/wycats/merb/blob/master/merb-core/lib/merb-core/rack/middleware/conditional_get.rb
|
||||
class ConditionalGet
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
# Return empty 304 response if the response has not been
|
||||
# modified since the last request.
|
||||
def call(env)
|
||||
case env[REQUEST_METHOD]
|
||||
when "GET", "HEAD"
|
||||
status, headers, body = @app.call(env)
|
||||
headers = Utils::HeaderHash[headers]
|
||||
if status == 200 && fresh?(env, headers)
|
||||
status = 304
|
||||
headers.delete(CONTENT_TYPE)
|
||||
headers.delete(CONTENT_LENGTH)
|
||||
original_body = body
|
||||
body = Rack::BodyProxy.new([]) do
|
||||
original_body.close if original_body.respond_to?(:close)
|
||||
end
|
||||
end
|
||||
[status, headers, body]
|
||||
else
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Return whether the response has not been modified since the
|
||||
# last request.
|
||||
def fresh?(env, headers)
|
||||
# If-None-Match has priority over If-Modified-Since per RFC 7232
|
||||
if none_match = env['HTTP_IF_NONE_MATCH']
|
||||
etag_matches?(none_match, headers)
|
||||
elsif (modified_since = env['HTTP_IF_MODIFIED_SINCE']) && (modified_since = to_rfc2822(modified_since))
|
||||
modified_since?(modified_since, headers)
|
||||
end
|
||||
end
|
||||
|
||||
# Whether the ETag response header matches the If-None-Match request header.
|
||||
# If so, the request has not been modified.
|
||||
def etag_matches?(none_match, headers)
|
||||
headers['ETag'] == none_match
|
||||
end
|
||||
|
||||
# Whether the Last-Modified response header matches the If-Modified-Since
|
||||
# request header. If so, the request has not been modified.
|
||||
def modified_since?(modified_since, headers)
|
||||
last_modified = to_rfc2822(headers['Last-Modified']) and
|
||||
modified_since >= last_modified
|
||||
end
|
||||
|
||||
# Return a Time object for the given string (which should be in RFC2822
|
||||
# format), or nil if the string cannot be parsed.
|
||||
def to_rfc2822(since)
|
||||
# shortest possible valid date is the obsolete: 1 Nov 97 09:55 A
|
||||
# anything shorter is invalid, this avoids exceptions for common cases
|
||||
# most common being the empty string
|
||||
if since && since.length >= 16
|
||||
# NOTE: there is no trivial way to write this in a non exception way
|
||||
# _rfc2822 returns a hash but is not that usable
|
||||
Time.rfc2822(since) rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/config.rb
vendored
Normal file
22
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/config.rb
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Rack::Config modifies the environment using the block given during
|
||||
# initialization.
|
||||
#
|
||||
# Example:
|
||||
# use Rack::Config do |env|
|
||||
# env['my-key'] = 'some-value'
|
||||
# end
|
||||
class Config
|
||||
def initialize(app, &block)
|
||||
@app = app
|
||||
@block = block
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@block.call(env)
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
38
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/content_length.rb
vendored
Normal file
38
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/content_length.rb
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
|
||||
# Sets the Content-Length header on responses that do not specify
|
||||
# a Content-Length or Transfer-Encoding header. Note that this
|
||||
# does not fix responses that have an invalid Content-Length
|
||||
# header specified.
|
||||
class ContentLength
|
||||
include Rack::Utils
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
headers = HeaderHash[headers]
|
||||
|
||||
if !STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&
|
||||
!headers[CONTENT_LENGTH] &&
|
||||
!headers[TRANSFER_ENCODING]
|
||||
|
||||
obody = body
|
||||
body, length = [], 0
|
||||
obody.each { |part| body << part; length += part.bytesize }
|
||||
|
||||
body = BodyProxy.new(body) do
|
||||
obody.close if obody.respond_to?(:close)
|
||||
end
|
||||
|
||||
headers[CONTENT_LENGTH] = length.to_s
|
||||
end
|
||||
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
end
|
||||
30
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/content_type.rb
vendored
Normal file
30
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/content_type.rb
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
|
||||
# Sets the Content-Type header on responses which don't have one.
|
||||
#
|
||||
# Builder Usage:
|
||||
# use Rack::ContentType, "text/plain"
|
||||
#
|
||||
# When no content type argument is provided, "text/html" is the
|
||||
# default.
|
||||
class ContentType
|
||||
include Rack::Utils
|
||||
|
||||
def initialize(app, content_type = "text/html")
|
||||
@app, @content_type = app, content_type
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
headers = Utils::HeaderHash[headers]
|
||||
|
||||
unless STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i)
|
||||
headers[CONTENT_TYPE] ||= @content_type
|
||||
end
|
||||
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
end
|
||||
14
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/core_ext/regexp.rb
vendored
Normal file
14
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/core_ext/regexp.rb
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Regexp has `match?` since Ruby 2.4
|
||||
# so to support Ruby < 2.4 we need to define this method
|
||||
|
||||
module Rack
|
||||
module RegexpExtensions
|
||||
refine Regexp do
|
||||
def match?(string, pos = 0)
|
||||
!!match(string, pos)
|
||||
end
|
||||
end unless //.respond_to?(:match?)
|
||||
end
|
||||
end
|
||||
144
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/deflater.rb
vendored
Normal file
144
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/deflater.rb
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "zlib"
|
||||
require "time" # for Time.httpdate
|
||||
|
||||
module Rack
|
||||
# This middleware enables content encoding of http responses,
|
||||
# usually for purposes of compression.
|
||||
#
|
||||
# Currently supported encodings:
|
||||
#
|
||||
# * gzip
|
||||
# * identity (no transformation)
|
||||
#
|
||||
# This middleware automatically detects when encoding is supported
|
||||
# and allowed. For example no encoding is made when a cache
|
||||
# directive of 'no-transform' is present, when the response status
|
||||
# code is one that doesn't allow an entity body, or when the body
|
||||
# is empty.
|
||||
#
|
||||
# Note that despite the name, Deflater does not support the +deflate+
|
||||
# encoding.
|
||||
class Deflater
|
||||
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
# Creates Rack::Deflater middleware. Options:
|
||||
#
|
||||
# :if :: a lambda enabling / disabling deflation based on returned boolean value
|
||||
# (e.g <tt>use Rack::Deflater, :if => lambda { |*, body| sum=0; body.each { |i| sum += i.length }; sum > 512 }</tt>).
|
||||
# However, be aware that calling `body.each` inside the block will break cases where `body.each` is not idempotent,
|
||||
# such as when it is an +IO+ instance.
|
||||
# :include :: a list of content types that should be compressed. By default, all content types are compressed.
|
||||
# :sync :: determines if the stream is going to be flushed after every chunk. Flushing after every chunk reduces
|
||||
# latency for time-sensitive streaming applications, but hurts compression and throughput.
|
||||
# Defaults to +true+.
|
||||
def initialize(app, options = {})
|
||||
@app = app
|
||||
@condition = options[:if]
|
||||
@compressible_types = options[:include]
|
||||
@sync = options.fetch(:sync, true)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
headers = Utils::HeaderHash[headers]
|
||||
|
||||
unless should_deflate?(env, status, headers, body)
|
||||
return [status, headers, body]
|
||||
end
|
||||
|
||||
request = Request.new(env)
|
||||
|
||||
encoding = Utils.select_best_encoding(%w(gzip identity),
|
||||
request.accept_encoding)
|
||||
|
||||
# Set the Vary HTTP header.
|
||||
vary = headers["Vary"].to_s.split(",").map(&:strip)
|
||||
unless vary.include?("*") || vary.include?("Accept-Encoding")
|
||||
headers["Vary"] = vary.push("Accept-Encoding").join(",")
|
||||
end
|
||||
|
||||
case encoding
|
||||
when "gzip"
|
||||
headers['Content-Encoding'] = "gzip"
|
||||
headers.delete(CONTENT_LENGTH)
|
||||
mtime = headers["Last-Modified"]
|
||||
mtime = Time.httpdate(mtime).to_i if mtime
|
||||
[status, headers, GzipStream.new(body, mtime, @sync)]
|
||||
when "identity"
|
||||
[status, headers, body]
|
||||
when nil
|
||||
message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
|
||||
bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) }
|
||||
[406, { CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s }, bp]
|
||||
end
|
||||
end
|
||||
|
||||
# Body class used for gzip encoded responses.
|
||||
class GzipStream
|
||||
# Initialize the gzip stream. Arguments:
|
||||
# body :: Response body to compress with gzip
|
||||
# mtime :: The modification time of the body, used to set the
|
||||
# modification time in the gzip header.
|
||||
# sync :: Whether to flush each gzip chunk as soon as it is ready.
|
||||
def initialize(body, mtime, sync)
|
||||
@body = body
|
||||
@mtime = mtime
|
||||
@sync = sync
|
||||
end
|
||||
|
||||
# Yield gzip compressed strings to the given block.
|
||||
def each(&block)
|
||||
@writer = block
|
||||
gzip = ::Zlib::GzipWriter.new(self)
|
||||
gzip.mtime = @mtime if @mtime
|
||||
@body.each { |part|
|
||||
# Skip empty strings, as they would result in no output,
|
||||
# and flushing empty parts would raise Zlib::BufError.
|
||||
next if part.empty?
|
||||
|
||||
gzip.write(part)
|
||||
gzip.flush if @sync
|
||||
}
|
||||
ensure
|
||||
gzip.close
|
||||
end
|
||||
|
||||
# Call the block passed to #each with the the gzipped data.
|
||||
def write(data)
|
||||
@writer.call(data)
|
||||
end
|
||||
|
||||
# Close the original body if possible.
|
||||
def close
|
||||
@body.close if @body.respond_to?(:close)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Whether the body should be compressed.
|
||||
def should_deflate?(env, status, headers, body)
|
||||
# Skip compressing empty entity body responses and responses with
|
||||
# no-transform set.
|
||||
if Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) ||
|
||||
/\bno-transform\b/.match?(headers['Cache-Control'].to_s) ||
|
||||
headers['Content-Encoding']&.!~(/\bidentity\b/)
|
||||
return false
|
||||
end
|
||||
|
||||
# Skip if @compressible_types are given and does not include request's content type
|
||||
return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/]))
|
||||
|
||||
# Skip if @condition lambda is given and evaluates to false
|
||||
return false if @condition && !@condition.call(env, status, headers, body)
|
||||
|
||||
# No point in compressing empty body, also handles usage with
|
||||
# Rack::Sendfile.
|
||||
return false if headers[CONTENT_LENGTH] == '0'
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
199
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/directory.rb
vendored
Normal file
199
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/directory.rb
vendored
Normal file
@ -0,0 +1,199 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'time'
|
||||
|
||||
module Rack
|
||||
# Rack::Directory serves entries below the +root+ given, according to the
|
||||
# path info of the Rack request. If a directory is found, the file's contents
|
||||
# will be presented in an html based index. If a file is found, the env will
|
||||
# be passed to the specified +app+.
|
||||
#
|
||||
# If +app+ is not specified, a Rack::Files of the same +root+ will be used.
|
||||
|
||||
class Directory
|
||||
DIR_FILE = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>\n"
|
||||
DIR_PAGE_HEADER = <<-PAGE
|
||||
<html><head>
|
||||
<title>%s</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style type='text/css'>
|
||||
table { width:100%%; }
|
||||
.name { text-align:left; }
|
||||
.size, .mtime { text-align:right; }
|
||||
.type { width:11em; }
|
||||
.mtime { width:15em; }
|
||||
</style>
|
||||
</head><body>
|
||||
<h1>%s</h1>
|
||||
<hr />
|
||||
<table>
|
||||
<tr>
|
||||
<th class='name'>Name</th>
|
||||
<th class='size'>Size</th>
|
||||
<th class='type'>Type</th>
|
||||
<th class='mtime'>Last Modified</th>
|
||||
</tr>
|
||||
PAGE
|
||||
DIR_PAGE_FOOTER = <<-PAGE
|
||||
</table>
|
||||
<hr />
|
||||
</body></html>
|
||||
PAGE
|
||||
|
||||
# Body class for directory entries, showing an index page with links
|
||||
# to each file.
|
||||
class DirectoryBody < Struct.new(:root, :path, :files)
|
||||
# Yield strings for each part of the directory entry
|
||||
def each
|
||||
show_path = Utils.escape_html(path.sub(/^#{root}/, ''))
|
||||
yield(DIR_PAGE_HEADER % [ show_path, show_path ])
|
||||
|
||||
unless path.chomp('/') == root
|
||||
yield(DIR_FILE % DIR_FILE_escape(files.call('..')))
|
||||
end
|
||||
|
||||
Dir.foreach(path) do |basename|
|
||||
next if basename.start_with?('.')
|
||||
next unless f = files.call(basename)
|
||||
yield(DIR_FILE % DIR_FILE_escape(f))
|
||||
end
|
||||
|
||||
yield(DIR_PAGE_FOOTER)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Escape each element in the array of html strings.
|
||||
def DIR_FILE_escape(htmls)
|
||||
htmls.map { |e| Utils.escape_html(e) }
|
||||
end
|
||||
end
|
||||
|
||||
# The root of the directory hierarchy. Only requests for files and
|
||||
# directories inside of the root directory are supported.
|
||||
attr_reader :root
|
||||
|
||||
# Set the root directory and application for serving files.
|
||||
def initialize(root, app = nil)
|
||||
@root = ::File.expand_path(root)
|
||||
@app = app || Files.new(@root)
|
||||
@head = Head.new(method(:get))
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# strip body if this is a HEAD call
|
||||
@head.call env
|
||||
end
|
||||
|
||||
# Internals of request handling. Similar to call but does
|
||||
# not remove body for HEAD requests.
|
||||
def get(env)
|
||||
script_name = env[SCRIPT_NAME]
|
||||
path_info = Utils.unescape_path(env[PATH_INFO])
|
||||
|
||||
if client_error_response = check_bad_request(path_info) || check_forbidden(path_info)
|
||||
client_error_response
|
||||
else
|
||||
path = ::File.join(@root, path_info)
|
||||
list_path(env, path, path_info, script_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Rack response to use for requests with invalid paths, or nil if path is valid.
|
||||
def check_bad_request(path_info)
|
||||
return if Utils.valid_path?(path_info)
|
||||
|
||||
body = "Bad Request\n"
|
||||
[400, { CONTENT_TYPE => "text/plain",
|
||||
CONTENT_LENGTH => body.bytesize.to_s,
|
||||
"X-Cascade" => "pass" }, [body]]
|
||||
end
|
||||
|
||||
# Rack response to use for requests with paths outside the root, or nil if path is inside the root.
|
||||
def check_forbidden(path_info)
|
||||
return unless path_info.include? ".."
|
||||
return if ::File.expand_path(::File.join(@root, path_info)).start_with?(@root)
|
||||
|
||||
body = "Forbidden\n"
|
||||
[403, { CONTENT_TYPE => "text/plain",
|
||||
CONTENT_LENGTH => body.bytesize.to_s,
|
||||
"X-Cascade" => "pass" }, [body]]
|
||||
end
|
||||
|
||||
# Rack response to use for directories under the root.
|
||||
def list_directory(path_info, path, script_name)
|
||||
url_head = (script_name.split('/') + path_info.split('/')).map do |part|
|
||||
Utils.escape_path part
|
||||
end
|
||||
|
||||
# Globbing not safe as path could contain glob metacharacters
|
||||
body = DirectoryBody.new(@root, path, ->(basename) do
|
||||
stat = stat(::File.join(path, basename))
|
||||
next unless stat
|
||||
|
||||
url = ::File.join(*url_head + [Utils.escape_path(basename)])
|
||||
mtime = stat.mtime.httpdate
|
||||
if stat.directory?
|
||||
type = 'directory'
|
||||
size = '-'
|
||||
url << '/'
|
||||
if basename == '..'
|
||||
basename = 'Parent Directory'
|
||||
else
|
||||
basename << '/'
|
||||
end
|
||||
else
|
||||
type = Mime.mime_type(::File.extname(basename))
|
||||
size = filesize_format(stat.size)
|
||||
end
|
||||
|
||||
[ url, basename, size, type, mtime ]
|
||||
end)
|
||||
|
||||
[ 200, { CONTENT_TYPE => 'text/html; charset=utf-8' }, body ]
|
||||
end
|
||||
|
||||
# File::Stat for the given path, but return nil for missing/bad entries.
|
||||
def stat(path)
|
||||
::File.stat(path)
|
||||
rescue Errno::ENOENT, Errno::ELOOP
|
||||
return nil
|
||||
end
|
||||
|
||||
# Rack response to use for files and directories under the root.
|
||||
# Unreadable and non-file, non-directory entries will get a 404 response.
|
||||
def list_path(env, path, path_info, script_name)
|
||||
if (stat = stat(path)) && stat.readable?
|
||||
return @app.call(env) if stat.file?
|
||||
return list_directory(path_info, path, script_name) if stat.directory?
|
||||
end
|
||||
|
||||
entity_not_found(path_info)
|
||||
end
|
||||
|
||||
# Rack response to use for unreadable and non-file, non-directory entries.
|
||||
def entity_not_found(path_info)
|
||||
body = "Entity not found: #{path_info}\n"
|
||||
[404, { CONTENT_TYPE => "text/plain",
|
||||
CONTENT_LENGTH => body.bytesize.to_s,
|
||||
"X-Cascade" => "pass" }, [body]]
|
||||
end
|
||||
|
||||
# Stolen from Ramaze
|
||||
FILESIZE_FORMAT = [
|
||||
['%.1fT', 1 << 40],
|
||||
['%.1fG', 1 << 30],
|
||||
['%.1fM', 1 << 20],
|
||||
['%.1fK', 1 << 10],
|
||||
]
|
||||
|
||||
# Provide human readable file sizes
|
||||
def filesize_format(int)
|
||||
FILESIZE_FORMAT.each do |format, size|
|
||||
return format % (int.to_f / size) if int >= size
|
||||
end
|
||||
|
||||
"#{int}B"
|
||||
end
|
||||
end
|
||||
end
|
||||
75
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/etag.rb
vendored
Normal file
75
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/etag.rb
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../rack'
|
||||
require 'digest/sha2'
|
||||
|
||||
module Rack
|
||||
# Automatically sets the ETag header on all String bodies.
|
||||
#
|
||||
# The ETag header is skipped if ETag or Last-Modified headers are sent or if
|
||||
# a sendfile body (body.responds_to :to_path) is given (since such cases
|
||||
# should be handled by apache/nginx).
|
||||
#
|
||||
# On initialization, you can pass two parameters: a Cache-Control directive
|
||||
# used when Etag is absent and a directive when it is present. The first
|
||||
# defaults to nil, while the second defaults to "max-age=0, private, must-revalidate"
|
||||
class ETag
|
||||
ETAG_STRING = Rack::ETAG
|
||||
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
|
||||
|
||||
def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL)
|
||||
@app = app
|
||||
@cache_control = cache_control
|
||||
@no_cache_control = no_cache_control
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
|
||||
if etag_status?(status) && etag_body?(body) && !skip_caching?(headers)
|
||||
original_body = body
|
||||
digest, new_body = digest_body(body)
|
||||
body = Rack::BodyProxy.new(new_body) do
|
||||
original_body.close if original_body.respond_to?(:close)
|
||||
end
|
||||
headers[ETAG_STRING] = %(W/"#{digest}") if digest
|
||||
end
|
||||
|
||||
unless headers[CACHE_CONTROL]
|
||||
if digest
|
||||
headers[CACHE_CONTROL] = @cache_control if @cache_control
|
||||
else
|
||||
headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control
|
||||
end
|
||||
end
|
||||
|
||||
[status, headers, body]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def etag_status?(status)
|
||||
status == 200 || status == 201
|
||||
end
|
||||
|
||||
def etag_body?(body)
|
||||
!body.respond_to?(:to_path)
|
||||
end
|
||||
|
||||
def skip_caching?(headers)
|
||||
headers.key?(ETAG_STRING) || headers.key?('Last-Modified')
|
||||
end
|
||||
|
||||
def digest_body(body)
|
||||
parts = []
|
||||
digest = nil
|
||||
|
||||
body.each do |part|
|
||||
parts << part
|
||||
(digest ||= Digest::SHA256.new) << part unless part.empty?
|
||||
end
|
||||
|
||||
[digest && digest.hexdigest.byteslice(0, 32), parts]
|
||||
end
|
||||
end
|
||||
end
|
||||
153
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/events.rb
vendored
Normal file
153
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/events.rb
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
### This middleware provides hooks to certain places in the request /
|
||||
# response lifecycle. This is so that middleware that don't need to filter
|
||||
# the response data can safely leave it alone and not have to send messages
|
||||
# down the traditional "rack stack".
|
||||
#
|
||||
# The events are:
|
||||
#
|
||||
# * on_start(request, response)
|
||||
#
|
||||
# This event is sent at the start of the request, before the next
|
||||
# middleware in the chain is called. This method is called with a request
|
||||
# object, and a response object. Right now, the response object is always
|
||||
# nil, but in the future it may actually be a real response object.
|
||||
#
|
||||
# * on_commit(request, response)
|
||||
#
|
||||
# The response has been committed. The application has returned, but the
|
||||
# response has not been sent to the webserver yet. This method is always
|
||||
# called with a request object and the response object. The response
|
||||
# object is constructed from the rack triple that the application returned.
|
||||
# Changes may still be made to the response object at this point.
|
||||
#
|
||||
# * on_send(request, response)
|
||||
#
|
||||
# The webserver has started iterating over the response body and presumably
|
||||
# has started sending data over the wire. This method is always called with
|
||||
# a request object and the response object. The response object is
|
||||
# constructed from the rack triple that the application returned. Changes
|
||||
# SHOULD NOT be made to the response object as the webserver has already
|
||||
# started sending data. Any mutations will likely result in an exception.
|
||||
#
|
||||
# * on_finish(request, response)
|
||||
#
|
||||
# The webserver has closed the response, and all data has been written to
|
||||
# the response socket. The request and response object should both be
|
||||
# read-only at this point. The body MAY NOT be available on the response
|
||||
# object as it may have been flushed to the socket.
|
||||
#
|
||||
# * on_error(request, response, error)
|
||||
#
|
||||
# An exception has occurred in the application or an `on_commit` event.
|
||||
# This method will get the request, the response (if available) and the
|
||||
# exception that was raised.
|
||||
#
|
||||
# ## Order
|
||||
#
|
||||
# `on_start` is called on the handlers in the order that they were passed to
|
||||
# the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are
|
||||
# called in the reverse order. `on_finish` handlers are called inside an
|
||||
# `ensure` block, so they are guaranteed to be called even if something
|
||||
# raises an exception. If something raises an exception in a `on_finish`
|
||||
# method, then nothing is guaranteed.
|
||||
|
||||
class Events
|
||||
module Abstract
|
||||
def on_start(req, res)
|
||||
end
|
||||
|
||||
def on_commit(req, res)
|
||||
end
|
||||
|
||||
def on_send(req, res)
|
||||
end
|
||||
|
||||
def on_finish(req, res)
|
||||
end
|
||||
|
||||
def on_error(req, res, e)
|
||||
end
|
||||
end
|
||||
|
||||
class EventedBodyProxy < Rack::BodyProxy # :nodoc:
|
||||
attr_reader :request, :response
|
||||
|
||||
def initialize(body, request, response, handlers, &block)
|
||||
super(body, &block)
|
||||
@request = request
|
||||
@response = response
|
||||
@handlers = handlers
|
||||
end
|
||||
|
||||
def each
|
||||
@handlers.reverse_each { |handler| handler.on_send request, response }
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
class BufferedResponse < Rack::Response::Raw # :nodoc:
|
||||
attr_reader :body
|
||||
|
||||
def initialize(status, headers, body)
|
||||
super(status, headers)
|
||||
@body = body
|
||||
end
|
||||
|
||||
def to_a; [status, headers, body]; end
|
||||
end
|
||||
|
||||
def initialize(app, handlers)
|
||||
@app = app
|
||||
@handlers = handlers
|
||||
end
|
||||
|
||||
def call(env)
|
||||
request = make_request env
|
||||
on_start request, nil
|
||||
|
||||
begin
|
||||
status, headers, body = @app.call request.env
|
||||
response = make_response status, headers, body
|
||||
on_commit request, response
|
||||
rescue StandardError => e
|
||||
on_error request, response, e
|
||||
on_finish request, response
|
||||
raise
|
||||
end
|
||||
|
||||
body = EventedBodyProxy.new(body, request, response, @handlers) do
|
||||
on_finish request, response
|
||||
end
|
||||
[response.status, response.headers, body]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def on_error(request, response, e)
|
||||
@handlers.reverse_each { |handler| handler.on_error request, response, e }
|
||||
end
|
||||
|
||||
def on_commit(request, response)
|
||||
@handlers.reverse_each { |handler| handler.on_commit request, response }
|
||||
end
|
||||
|
||||
def on_start(request, response)
|
||||
@handlers.each { |handler| handler.on_start request, nil }
|
||||
end
|
||||
|
||||
def on_finish(request, response)
|
||||
@handlers.reverse_each { |handler| handler.on_finish request, response }
|
||||
end
|
||||
|
||||
def make_request(env)
|
||||
Rack::Request.new env
|
||||
end
|
||||
|
||||
def make_response(status, headers, body)
|
||||
BufferedResponse.new status, headers, body
|
||||
end
|
||||
end
|
||||
end
|
||||
7
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/file.rb
vendored
Normal file
7
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/file.rb
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'files'
|
||||
|
||||
module Rack
|
||||
File = Files
|
||||
end
|
||||
218
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/files.rb
vendored
Normal file
218
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/files.rb
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'time'
|
||||
|
||||
module Rack
|
||||
# Rack::Files serves files below the +root+ directory given, according to the
|
||||
# path info of the Rack request.
|
||||
# e.g. when Rack::Files.new("/etc") is used, you can access 'passwd' file
|
||||
# as http://localhost:9292/passwd
|
||||
#
|
||||
# Handlers can detect if bodies are a Rack::Files, and use mechanisms
|
||||
# like sendfile on the +path+.
|
||||
|
||||
class Files
|
||||
ALLOWED_VERBS = %w[GET HEAD OPTIONS]
|
||||
ALLOW_HEADER = ALLOWED_VERBS.join(', ')
|
||||
MULTIPART_BOUNDARY = 'AaB03x'
|
||||
|
||||
# @todo remove in 3.0
|
||||
def self.method_added(name)
|
||||
if name == :response_body
|
||||
raise "#{self.class}\#response_body is no longer supported."
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
attr_reader :root
|
||||
|
||||
def initialize(root, headers = {}, default_mime = 'text/plain')
|
||||
@root = (::File.expand_path(root) if root)
|
||||
@headers = headers
|
||||
@default_mime = default_mime
|
||||
@head = Rack::Head.new(lambda { |env| get env })
|
||||
end
|
||||
|
||||
def call(env)
|
||||
# HEAD requests drop the response body, including 4xx error messages.
|
||||
@head.call env
|
||||
end
|
||||
|
||||
def get(env)
|
||||
request = Rack::Request.new env
|
||||
unless ALLOWED_VERBS.include? request.request_method
|
||||
return fail(405, "Method Not Allowed", { 'Allow' => ALLOW_HEADER })
|
||||
end
|
||||
|
||||
path_info = Utils.unescape_path request.path_info
|
||||
return fail(400, "Bad Request") unless Utils.valid_path?(path_info)
|
||||
|
||||
clean_path_info = Utils.clean_path_info(path_info)
|
||||
path = ::File.join(@root, clean_path_info)
|
||||
|
||||
available = begin
|
||||
::File.file?(path) && ::File.readable?(path)
|
||||
rescue SystemCallError
|
||||
# Not sure in what conditions this exception can occur, but this
|
||||
# is a safe way to handle such an error.
|
||||
# :nocov:
|
||||
false
|
||||
# :nocov:
|
||||
end
|
||||
|
||||
if available
|
||||
serving(request, path)
|
||||
else
|
||||
fail(404, "File not found: #{path_info}")
|
||||
end
|
||||
end
|
||||
|
||||
def serving(request, path)
|
||||
if request.options?
|
||||
return [200, { 'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0' }, []]
|
||||
end
|
||||
last_modified = ::File.mtime(path).httpdate
|
||||
return [304, {}, []] if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
|
||||
|
||||
headers = { "Last-Modified" => last_modified }
|
||||
mime_type = mime_type path, @default_mime
|
||||
headers[CONTENT_TYPE] = mime_type if mime_type
|
||||
|
||||
# Set custom headers
|
||||
headers.merge!(@headers) if @headers
|
||||
|
||||
status = 200
|
||||
size = filesize path
|
||||
|
||||
ranges = Rack::Utils.get_byte_ranges(request.get_header('HTTP_RANGE'), size)
|
||||
if ranges.nil?
|
||||
# No ranges:
|
||||
ranges = [0..size - 1]
|
||||
elsif ranges.empty?
|
||||
# Unsatisfiable. Return error, and file size:
|
||||
response = fail(416, "Byte range unsatisfiable")
|
||||
response[1]["Content-Range"] = "bytes */#{size}"
|
||||
return response
|
||||
elsif ranges.size >= 1
|
||||
# Partial content
|
||||
partial_content = true
|
||||
|
||||
if ranges.size == 1
|
||||
range = ranges[0]
|
||||
headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
|
||||
else
|
||||
headers[CONTENT_TYPE] = "multipart/byteranges; boundary=#{MULTIPART_BOUNDARY}"
|
||||
end
|
||||
|
||||
status = 206
|
||||
body = BaseIterator.new(path, ranges, mime_type: mime_type, size: size)
|
||||
size = body.bytesize
|
||||
end
|
||||
|
||||
headers[CONTENT_LENGTH] = size.to_s
|
||||
|
||||
if request.head?
|
||||
body = []
|
||||
elsif !partial_content
|
||||
body = Iterator.new(path, ranges, mime_type: mime_type, size: size)
|
||||
end
|
||||
|
||||
[status, headers, body]
|
||||
end
|
||||
|
||||
class BaseIterator
|
||||
attr_reader :path, :ranges, :options
|
||||
|
||||
def initialize(path, ranges, options)
|
||||
@path = path
|
||||
@ranges = ranges
|
||||
@options = options
|
||||
end
|
||||
|
||||
def each
|
||||
::File.open(path, "rb") do |file|
|
||||
ranges.each do |range|
|
||||
yield multipart_heading(range) if multipart?
|
||||
|
||||
each_range_part(file, range) do |part|
|
||||
yield part
|
||||
end
|
||||
end
|
||||
|
||||
yield "\r\n--#{MULTIPART_BOUNDARY}--\r\n" if multipart?
|
||||
end
|
||||
end
|
||||
|
||||
def bytesize
|
||||
size = ranges.inject(0) do |sum, range|
|
||||
sum += multipart_heading(range).bytesize if multipart?
|
||||
sum += range.size
|
||||
end
|
||||
size += "\r\n--#{MULTIPART_BOUNDARY}--\r\n".bytesize if multipart?
|
||||
size
|
||||
end
|
||||
|
||||
def close; end
|
||||
|
||||
private
|
||||
|
||||
def multipart?
|
||||
ranges.size > 1
|
||||
end
|
||||
|
||||
def multipart_heading(range)
|
||||
<<-EOF
|
||||
\r
|
||||
--#{MULTIPART_BOUNDARY}\r
|
||||
Content-Type: #{options[:mime_type]}\r
|
||||
Content-Range: bytes #{range.begin}-#{range.end}/#{options[:size]}\r
|
||||
\r
|
||||
EOF
|
||||
end
|
||||
|
||||
def each_range_part(file, range)
|
||||
file.seek(range.begin)
|
||||
remaining_len = range.end - range.begin + 1
|
||||
while remaining_len > 0
|
||||
part = file.read([8192, remaining_len].min)
|
||||
break unless part
|
||||
remaining_len -= part.length
|
||||
|
||||
yield part
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Iterator < BaseIterator
|
||||
alias :to_path :path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fail(status, body, headers = {})
|
||||
body += "\n"
|
||||
|
||||
[
|
||||
status,
|
||||
{
|
||||
CONTENT_TYPE => "text/plain",
|
||||
CONTENT_LENGTH => body.size.to_s,
|
||||
"X-Cascade" => "pass"
|
||||
}.merge!(headers),
|
||||
[body]
|
||||
]
|
||||
end
|
||||
|
||||
# The MIME type for the contents of the file located at @path
|
||||
def mime_type(path, default_mime)
|
||||
Mime.mime_type(::File.extname(path), default_mime)
|
||||
end
|
||||
|
||||
def filesize(path)
|
||||
# We check via File::size? whether this file provides size info
|
||||
# via stat (e.g. /proc files often don't), otherwise we have to
|
||||
# figure it out by reading the whole file into memory.
|
||||
::File.size?(path) || ::File.read(path).bytesize
|
||||
end
|
||||
end
|
||||
end
|
||||
104
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler.rb
vendored
Normal file
104
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler.rb
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# *Handlers* connect web servers with Rack.
|
||||
#
|
||||
# Rack includes Handlers for Thin, WEBrick, FastCGI, CGI, SCGI
|
||||
# and LiteSpeed.
|
||||
#
|
||||
# Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
|
||||
# A second optional hash can be passed to include server-specific
|
||||
# configuration.
|
||||
module Handler
|
||||
def self.get(server)
|
||||
return unless server
|
||||
server = server.to_s
|
||||
|
||||
unless @handlers.include? server
|
||||
load_error = try_require('rack/handler', server)
|
||||
end
|
||||
|
||||
if klass = @handlers[server]
|
||||
const_get(klass)
|
||||
else
|
||||
const_get(server, false)
|
||||
end
|
||||
|
||||
rescue NameError => name_error
|
||||
raise load_error || name_error
|
||||
end
|
||||
|
||||
# Select first available Rack handler given an `Array` of server names.
|
||||
# Raises `LoadError` if no handler was found.
|
||||
#
|
||||
# > pick ['thin', 'webrick']
|
||||
# => Rack::Handler::WEBrick
|
||||
def self.pick(server_names)
|
||||
server_names = Array(server_names)
|
||||
server_names.each do |server_name|
|
||||
begin
|
||||
return get(server_name.to_s)
|
||||
rescue LoadError, NameError
|
||||
end
|
||||
end
|
||||
|
||||
raise LoadError, "Couldn't find handler for: #{server_names.join(', ')}."
|
||||
end
|
||||
|
||||
SERVER_NAMES = %w(puma thin falcon webrick).freeze
|
||||
private_constant :SERVER_NAMES
|
||||
|
||||
def self.default
|
||||
# Guess.
|
||||
if ENV.include?("PHP_FCGI_CHILDREN")
|
||||
Rack::Handler::FastCGI
|
||||
elsif ENV.include?(REQUEST_METHOD)
|
||||
Rack::Handler::CGI
|
||||
elsif ENV.include?("RACK_HANDLER")
|
||||
self.get(ENV["RACK_HANDLER"])
|
||||
else
|
||||
pick SERVER_NAMES
|
||||
end
|
||||
end
|
||||
|
||||
# Transforms server-name constants to their canonical form as filenames,
|
||||
# then tries to require them but silences the LoadError if not found
|
||||
#
|
||||
# Naming convention:
|
||||
#
|
||||
# Foo # => 'foo'
|
||||
# FooBar # => 'foo_bar.rb'
|
||||
# FooBAR # => 'foobar.rb'
|
||||
# FOObar # => 'foobar.rb'
|
||||
# FOOBAR # => 'foobar.rb'
|
||||
# FooBarBaz # => 'foo_bar_baz.rb'
|
||||
def self.try_require(prefix, const_name)
|
||||
file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
|
||||
gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
|
||||
|
||||
require(::File.join(prefix, file))
|
||||
nil
|
||||
rescue LoadError => error
|
||||
error
|
||||
end
|
||||
|
||||
def self.register(server, klass)
|
||||
@handlers ||= {}
|
||||
@handlers[server.to_s] = klass.to_s
|
||||
end
|
||||
|
||||
autoload :CGI, "rack/handler/cgi"
|
||||
autoload :FastCGI, "rack/handler/fastcgi"
|
||||
autoload :WEBrick, "rack/handler/webrick"
|
||||
autoload :LSWS, "rack/handler/lsws"
|
||||
autoload :SCGI, "rack/handler/scgi"
|
||||
autoload :Thin, "rack/handler/thin"
|
||||
|
||||
register 'cgi', 'Rack::Handler::CGI'
|
||||
register 'fastcgi', 'Rack::Handler::FastCGI'
|
||||
register 'webrick', 'Rack::Handler::WEBrick'
|
||||
register 'lsws', 'Rack::Handler::LSWS'
|
||||
register 'scgi', 'Rack::Handler::SCGI'
|
||||
register 'thin', 'Rack::Handler::Thin'
|
||||
end
|
||||
end
|
||||
59
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/cgi.rb
vendored
Normal file
59
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/cgi.rb
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
module Handler
|
||||
class CGI
|
||||
def self.run(app, **options)
|
||||
$stdin.binmode
|
||||
serve app
|
||||
end
|
||||
|
||||
def self.serve(app)
|
||||
env = ENV.to_hash
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
|
||||
env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
|
||||
|
||||
env.update(
|
||||
RACK_VERSION => Rack::VERSION,
|
||||
RACK_INPUT => Rack::RewindableInput.new($stdin),
|
||||
RACK_ERRORS => $stderr,
|
||||
RACK_MULTITHREAD => false,
|
||||
RACK_MULTIPROCESS => true,
|
||||
RACK_RUNONCE => true,
|
||||
RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http"
|
||||
)
|
||||
|
||||
env[QUERY_STRING] ||= ""
|
||||
env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
|
||||
env[REQUEST_PATH] ||= "/"
|
||||
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
send_headers status, headers
|
||||
send_body body
|
||||
ensure
|
||||
body.close if body.respond_to? :close
|
||||
end
|
||||
end
|
||||
|
||||
def self.send_headers(status, headers)
|
||||
$stdout.print "Status: #{status}\r\n"
|
||||
headers.each { |k, vs|
|
||||
vs.split("\n").each { |v|
|
||||
$stdout.print "#{k}: #{v}\r\n"
|
||||
}
|
||||
}
|
||||
$stdout.print "\r\n"
|
||||
$stdout.flush
|
||||
end
|
||||
|
||||
def self.send_body(body)
|
||||
body.each { |part|
|
||||
$stdout.print part
|
||||
$stdout.flush
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
100
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/fastcgi.rb
vendored
Normal file
100
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/fastcgi.rb
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'fcgi'
|
||||
require 'socket'
|
||||
|
||||
if defined? FCGI::Stream
|
||||
class FCGI::Stream
|
||||
alias _rack_read_without_buffer read
|
||||
|
||||
def read(n, buffer = nil)
|
||||
buf = _rack_read_without_buffer n
|
||||
buffer.replace(buf.to_s) if buffer
|
||||
buf
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Rack
|
||||
module Handler
|
||||
class FastCGI
|
||||
def self.run(app, **options)
|
||||
if options[:File]
|
||||
STDIN.reopen(UNIXServer.new(options[:File]))
|
||||
elsif options[:Port]
|
||||
STDIN.reopen(TCPServer.new(options[:Host], options[:Port]))
|
||||
end
|
||||
FCGI.each { |request|
|
||||
serve request, app
|
||||
}
|
||||
end
|
||||
|
||||
def self.valid_options
|
||||
environment = ENV['RACK_ENV'] || 'development'
|
||||
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
||||
|
||||
{
|
||||
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
||||
"Port=PORT" => "Port to listen on (default: 8080)",
|
||||
"File=PATH" => "Creates a Domain socket at PATH instead of a TCP socket. Ignores Host and Port if set.",
|
||||
}
|
||||
end
|
||||
|
||||
def self.serve(request, app)
|
||||
env = request.env
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
|
||||
env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
|
||||
|
||||
rack_input = RewindableInput.new(request.in)
|
||||
|
||||
env.update(
|
||||
RACK_VERSION => Rack::VERSION,
|
||||
RACK_INPUT => rack_input,
|
||||
RACK_ERRORS => request.err,
|
||||
RACK_MULTITHREAD => false,
|
||||
RACK_MULTIPROCESS => true,
|
||||
RACK_RUNONCE => false,
|
||||
RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http"
|
||||
)
|
||||
|
||||
env[QUERY_STRING] ||= ""
|
||||
env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
|
||||
env[REQUEST_PATH] ||= "/"
|
||||
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
|
||||
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
|
||||
|
||||
begin
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
send_headers request.out, status, headers
|
||||
send_body request.out, body
|
||||
ensure
|
||||
body.close if body.respond_to? :close
|
||||
end
|
||||
ensure
|
||||
rack_input.close
|
||||
request.finish
|
||||
end
|
||||
end
|
||||
|
||||
def self.send_headers(out, status, headers)
|
||||
out.print "Status: #{status}\r\n"
|
||||
headers.each { |k, vs|
|
||||
vs.split("\n").each { |v|
|
||||
out.print "#{k}: #{v}\r\n"
|
||||
}
|
||||
}
|
||||
out.print "\r\n"
|
||||
out.flush
|
||||
end
|
||||
|
||||
def self.send_body(out, body)
|
||||
body.each { |part|
|
||||
out.print part
|
||||
out.flush
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
61
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/lsws.rb
vendored
Normal file
61
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/lsws.rb
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'lsapi'
|
||||
|
||||
module Rack
|
||||
module Handler
|
||||
class LSWS
|
||||
def self.run(app, **options)
|
||||
while LSAPI.accept != nil
|
||||
serve app
|
||||
end
|
||||
end
|
||||
def self.serve(app)
|
||||
env = ENV.to_hash
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
env[SCRIPT_NAME] = "" if env[SCRIPT_NAME] == "/"
|
||||
|
||||
rack_input = RewindableInput.new($stdin.read.to_s)
|
||||
|
||||
env.update(
|
||||
RACK_VERSION => Rack::VERSION,
|
||||
RACK_INPUT => rack_input,
|
||||
RACK_ERRORS => $stderr,
|
||||
RACK_MULTITHREAD => false,
|
||||
RACK_MULTIPROCESS => true,
|
||||
RACK_RUNONCE => false,
|
||||
RACK_URL_SCHEME => ["yes", "on", "1"].include?(ENV[HTTPS]) ? "https" : "http"
|
||||
)
|
||||
|
||||
env[QUERY_STRING] ||= ""
|
||||
env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
|
||||
env[REQUEST_PATH] ||= "/"
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
send_headers status, headers
|
||||
send_body body
|
||||
ensure
|
||||
body.close if body.respond_to? :close
|
||||
end
|
||||
ensure
|
||||
rack_input.close
|
||||
end
|
||||
def self.send_headers(status, headers)
|
||||
print "Status: #{status}\r\n"
|
||||
headers.each { |k, vs|
|
||||
vs.split("\n").each { |v|
|
||||
print "#{k}: #{v}\r\n"
|
||||
}
|
||||
}
|
||||
print "\r\n"
|
||||
STDOUT.flush
|
||||
end
|
||||
def self.send_body(body)
|
||||
body.each { |part|
|
||||
print part
|
||||
STDOUT.flush
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
71
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/scgi.rb
vendored
Normal file
71
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/scgi.rb
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'scgi'
|
||||
require 'stringio'
|
||||
|
||||
module Rack
|
||||
module Handler
|
||||
class SCGI < ::SCGI::Processor
|
||||
attr_accessor :app
|
||||
|
||||
def self.run(app, **options)
|
||||
options[:Socket] = UNIXServer.new(options[:File]) if options[:File]
|
||||
new(options.merge(app: app,
|
||||
host: options[:Host],
|
||||
port: options[:Port],
|
||||
socket: options[:Socket])).listen
|
||||
end
|
||||
|
||||
def self.valid_options
|
||||
environment = ENV['RACK_ENV'] || 'development'
|
||||
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
||||
|
||||
{
|
||||
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
||||
"Port=PORT" => "Port to listen on (default: 8080)",
|
||||
}
|
||||
end
|
||||
|
||||
def initialize(settings = {})
|
||||
@app = settings[:app]
|
||||
super(settings)
|
||||
end
|
||||
|
||||
def process_request(request, input_body, socket)
|
||||
env = Hash[request]
|
||||
env.delete "HTTP_CONTENT_TYPE"
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
env[REQUEST_PATH], env[QUERY_STRING] = env["REQUEST_URI"].split('?', 2)
|
||||
env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
|
||||
env[PATH_INFO] = env[REQUEST_PATH]
|
||||
env[QUERY_STRING] ||= ""
|
||||
env[SCRIPT_NAME] = ""
|
||||
|
||||
rack_input = StringIO.new(input_body)
|
||||
rack_input.set_encoding(Encoding::BINARY)
|
||||
|
||||
env.update(
|
||||
RACK_VERSION => Rack::VERSION,
|
||||
RACK_INPUT => rack_input,
|
||||
RACK_ERRORS => $stderr,
|
||||
RACK_MULTITHREAD => true,
|
||||
RACK_MULTIPROCESS => true,
|
||||
RACK_RUNONCE => false,
|
||||
RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http"
|
||||
)
|
||||
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
socket.write("Status: #{status}\r\n")
|
||||
headers.each do |k, vs|
|
||||
vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")}
|
||||
end
|
||||
socket.write("\r\n")
|
||||
body.each {|s| socket.write(s)}
|
||||
ensure
|
||||
body.close if body.respond_to? :close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/thin.rb
vendored
Normal file
36
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/thin.rb
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "thin"
|
||||
require "thin/server"
|
||||
require "thin/logging"
|
||||
require "thin/backends/tcp_server"
|
||||
|
||||
module Rack
|
||||
module Handler
|
||||
class Thin
|
||||
def self.run(app, **options)
|
||||
environment = ENV['RACK_ENV'] || 'development'
|
||||
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
||||
|
||||
host = options.delete(:Host) || default_host
|
||||
port = options.delete(:Port) || 8080
|
||||
args = [host, port, app, options]
|
||||
# Thin versions below 0.8.0 do not support additional options
|
||||
args.pop if ::Thin::VERSION::MAJOR < 1 && ::Thin::VERSION::MINOR < 8
|
||||
server = ::Thin::Server.new(*args)
|
||||
yield server if block_given?
|
||||
server.start
|
||||
end
|
||||
|
||||
def self.valid_options
|
||||
environment = ENV['RACK_ENV'] || 'development'
|
||||
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
||||
|
||||
{
|
||||
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
||||
"Port=PORT" => "Port to listen on (default: 8080)",
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
129
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/webrick.rb
vendored
Normal file
129
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/handler/webrick.rb
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'webrick'
|
||||
require 'stringio'
|
||||
|
||||
# This monkey patch allows for applications to perform their own chunking
|
||||
# through WEBrick::HTTPResponse if rack is set to true.
|
||||
class WEBrick::HTTPResponse
|
||||
attr_accessor :rack
|
||||
|
||||
alias _rack_setup_header setup_header
|
||||
def setup_header
|
||||
app_chunking = rack && @header['transfer-encoding'] == 'chunked'
|
||||
|
||||
@chunked = app_chunking if app_chunking
|
||||
|
||||
_rack_setup_header
|
||||
|
||||
@chunked = false if app_chunking
|
||||
end
|
||||
end
|
||||
|
||||
module Rack
|
||||
module Handler
|
||||
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
|
||||
def self.run(app, **options)
|
||||
environment = ENV['RACK_ENV'] || 'development'
|
||||
default_host = environment == 'development' ? 'localhost' : nil
|
||||
|
||||
if !options[:BindAddress] || options[:Host]
|
||||
options[:BindAddress] = options.delete(:Host) || default_host
|
||||
end
|
||||
options[:Port] ||= 8080
|
||||
if options[:SSLEnable]
|
||||
require 'webrick/https'
|
||||
end
|
||||
|
||||
@server = ::WEBrick::HTTPServer.new(options)
|
||||
@server.mount "/", Rack::Handler::WEBrick, app
|
||||
yield @server if block_given?
|
||||
@server.start
|
||||
end
|
||||
|
||||
def self.valid_options
|
||||
environment = ENV['RACK_ENV'] || 'development'
|
||||
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
||||
|
||||
{
|
||||
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
||||
"Port=PORT" => "Port to listen on (default: 8080)",
|
||||
}
|
||||
end
|
||||
|
||||
def self.shutdown
|
||||
if @server
|
||||
@server.shutdown
|
||||
@server = nil
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(server, app)
|
||||
super server
|
||||
@app = app
|
||||
end
|
||||
|
||||
def service(req, res)
|
||||
res.rack = true
|
||||
env = req.meta_vars
|
||||
env.delete_if { |k, v| v.nil? }
|
||||
|
||||
rack_input = StringIO.new(req.body.to_s)
|
||||
rack_input.set_encoding(Encoding::BINARY)
|
||||
|
||||
env.update(
|
||||
RACK_VERSION => Rack::VERSION,
|
||||
RACK_INPUT => rack_input,
|
||||
RACK_ERRORS => $stderr,
|
||||
RACK_MULTITHREAD => true,
|
||||
RACK_MULTIPROCESS => false,
|
||||
RACK_RUNONCE => false,
|
||||
RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http",
|
||||
RACK_IS_HIJACK => true,
|
||||
RACK_HIJACK => lambda { raise NotImplementedError, "only partial hijack is supported."},
|
||||
RACK_HIJACK_IO => nil
|
||||
)
|
||||
|
||||
env[HTTP_VERSION] ||= env[SERVER_PROTOCOL]
|
||||
env[QUERY_STRING] ||= ""
|
||||
unless env[PATH_INFO] == ""
|
||||
path, n = req.request_uri.path, env[SCRIPT_NAME].length
|
||||
env[PATH_INFO] = path[n, path.length - n]
|
||||
end
|
||||
env[REQUEST_PATH] ||= [env[SCRIPT_NAME], env[PATH_INFO]].join
|
||||
|
||||
status, headers, body = @app.call(env)
|
||||
begin
|
||||
res.status = status.to_i
|
||||
io_lambda = nil
|
||||
headers.each { |k, vs|
|
||||
if k == RACK_HIJACK
|
||||
io_lambda = vs
|
||||
elsif k.downcase == "set-cookie"
|
||||
res.cookies.concat vs.split("\n")
|
||||
else
|
||||
# Since WEBrick won't accept repeated headers,
|
||||
# merge the values per RFC 1945 section 4.2.
|
||||
res[k] = vs.split("\n").join(", ")
|
||||
end
|
||||
}
|
||||
|
||||
if io_lambda
|
||||
rd, wr = IO.pipe
|
||||
res.body = rd
|
||||
res.chunked = true
|
||||
io_lambda.call wr
|
||||
elsif body.respond_to?(:to_path)
|
||||
res.body = ::File.open(body.to_path, 'rb')
|
||||
else
|
||||
body.each { |part|
|
||||
res.body << part
|
||||
}
|
||||
end
|
||||
ensure
|
||||
body.close if body.respond_to? :close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
25
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/head.rb
vendored
Normal file
25
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/head.rb
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Rack::Head returns an empty body for all HEAD requests. It leaves
|
||||
# all other requests unchanged.
|
||||
class Head
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
|
||||
if env[REQUEST_METHOD] == HEAD
|
||||
[
|
||||
status, headers, Rack::BodyProxy.new([]) do
|
||||
body.close if body.respond_to? :close
|
||||
end
|
||||
]
|
||||
else
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
806
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/lint.rb
vendored
Normal file
806
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/lint.rb
vendored
Normal file
@ -0,0 +1,806 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'forwardable'
|
||||
|
||||
module Rack
|
||||
# Rack::Lint validates your application and the requests and
|
||||
# responses according to the Rack spec.
|
||||
|
||||
class Lint
|
||||
def initialize(app)
|
||||
@app = app
|
||||
@content_length = nil
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
class LintError < RuntimeError; end
|
||||
module Assertion
|
||||
def assert(message)
|
||||
unless yield
|
||||
raise LintError, message
|
||||
end
|
||||
end
|
||||
end
|
||||
include Assertion
|
||||
|
||||
## This specification aims to formalize the Rack protocol. You
|
||||
## can (and should) use Rack::Lint to enforce it.
|
||||
##
|
||||
## When you develop middleware, be sure to add a Lint before and
|
||||
## after to catch all mistakes.
|
||||
|
||||
## = Rack applications
|
||||
|
||||
## A Rack application is a Ruby object (not a class) that
|
||||
## responds to +call+.
|
||||
def call(env = nil)
|
||||
dup._call(env)
|
||||
end
|
||||
|
||||
def _call(env)
|
||||
## It takes exactly one argument, the *environment*
|
||||
assert("No env given") { env }
|
||||
check_env env
|
||||
|
||||
env[RACK_INPUT] = InputWrapper.new(env[RACK_INPUT])
|
||||
env[RACK_ERRORS] = ErrorWrapper.new(env[RACK_ERRORS])
|
||||
|
||||
## and returns an Array of exactly three values:
|
||||
ary = @app.call(env)
|
||||
assert("response #{ary.inspect} is not an Array , but #{ary.class}") {
|
||||
ary.kind_of? Array
|
||||
}
|
||||
assert("response array #{ary.inspect} has #{ary.size} elements instead of 3") {
|
||||
ary.size == 3
|
||||
}
|
||||
|
||||
status, headers, @body = ary
|
||||
## The *status*,
|
||||
check_status status
|
||||
## the *headers*,
|
||||
check_headers headers
|
||||
|
||||
hijack_proc = check_hijack_response headers, env
|
||||
if hijack_proc && headers.is_a?(Hash)
|
||||
headers[RACK_HIJACK] = hijack_proc
|
||||
end
|
||||
|
||||
## and the *body*.
|
||||
check_content_type status, headers
|
||||
check_content_length status, headers
|
||||
@head_request = env[REQUEST_METHOD] == HEAD
|
||||
[status, headers, self]
|
||||
end
|
||||
|
||||
## == The Environment
|
||||
def check_env(env)
|
||||
## The environment must be an unfrozen instance of Hash that includes
|
||||
## CGI-like headers. The application is free to modify the
|
||||
## environment.
|
||||
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
|
||||
env.kind_of? Hash
|
||||
}
|
||||
assert("env should not be frozen, but is") {
|
||||
!env.frozen?
|
||||
}
|
||||
|
||||
##
|
||||
## The environment is required to include these variables
|
||||
## (adopted from PEP333), except when they'd be empty, but see
|
||||
## below.
|
||||
|
||||
## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
|
||||
## "GET" or "POST". This cannot ever
|
||||
## be an empty string, and so is
|
||||
## always required.
|
||||
|
||||
## <tt>SCRIPT_NAME</tt>:: The initial portion of the request
|
||||
## URL's "path" that corresponds to the
|
||||
## application object, so that the
|
||||
## application knows its virtual
|
||||
## "location". This may be an empty
|
||||
## string, if the application corresponds
|
||||
## to the "root" of the server.
|
||||
|
||||
## <tt>PATH_INFO</tt>:: The remainder of the request URL's
|
||||
## "path", designating the virtual
|
||||
## "location" of the request's target
|
||||
## within the application. This may be an
|
||||
## empty string, if the request URL targets
|
||||
## the application root and does not have a
|
||||
## trailing slash. This value may be
|
||||
## percent-encoded when originating from
|
||||
## a URL.
|
||||
|
||||
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
|
||||
## follows the <tt>?</tt>, if any. May be
|
||||
## empty, but is always required!
|
||||
|
||||
## <tt>SERVER_NAME</tt>:: When combined with <tt>SCRIPT_NAME</tt> and
|
||||
## <tt>PATH_INFO</tt>, these variables can be
|
||||
## used to complete the URL. Note, however,
|
||||
## that <tt>HTTP_HOST</tt>, if present,
|
||||
## should be used in preference to
|
||||
## <tt>SERVER_NAME</tt> for reconstructing
|
||||
## the request URL.
|
||||
## <tt>SERVER_NAME</tt> can never be an empty
|
||||
## string, and so is always required.
|
||||
|
||||
## <tt>SERVER_PORT</tt>:: An optional +Integer+ which is the port the
|
||||
## server is running on. Should be specified if
|
||||
## the server is running on a non-standard port.
|
||||
|
||||
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
|
||||
## client-supplied HTTP request
|
||||
## headers (i.e., variables whose
|
||||
## names begin with <tt>HTTP_</tt>). The
|
||||
## presence or absence of these
|
||||
## variables should correspond with
|
||||
## the presence or absence of the
|
||||
## appropriate HTTP header in the
|
||||
## request. See
|
||||
## {RFC3875 section 4.1.18}[https://tools.ietf.org/html/rfc3875#section-4.1.18]
|
||||
## for specific behavior.
|
||||
|
||||
## In addition to this, the Rack environment must include these
|
||||
## Rack-specific variables:
|
||||
|
||||
## <tt>rack.version</tt>:: The Array representing this version of Rack
|
||||
## See Rack::VERSION, that corresponds to
|
||||
## the version of this SPEC.
|
||||
|
||||
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
|
||||
## request URL.
|
||||
|
||||
## <tt>rack.input</tt>:: See below, the input stream.
|
||||
|
||||
## <tt>rack.errors</tt>:: See below, the error stream.
|
||||
|
||||
## <tt>rack.multithread</tt>:: true if the application object may be
|
||||
## simultaneously invoked by another thread
|
||||
## in the same process, false otherwise.
|
||||
|
||||
## <tt>rack.multiprocess</tt>:: true if an equivalent application object
|
||||
## may be simultaneously invoked by another
|
||||
## process, false otherwise.
|
||||
|
||||
## <tt>rack.run_once</tt>:: true if the server expects
|
||||
## (but does not guarantee!) that the
|
||||
## application will only be invoked this one
|
||||
## time during the life of its containing
|
||||
## process. Normally, this will only be true
|
||||
## for a server based on CGI
|
||||
## (or something similar).
|
||||
|
||||
## <tt>rack.hijack?</tt>:: present and true if the server supports
|
||||
## connection hijacking. See below, hijacking.
|
||||
|
||||
## <tt>rack.hijack</tt>:: an object responding to #call that must be
|
||||
## called at least once before using
|
||||
## rack.hijack_io.
|
||||
## It is recommended #call return rack.hijack_io
|
||||
## as well as setting it in env if necessary.
|
||||
|
||||
## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
|
||||
## has received #call, this will contain
|
||||
## an object resembling an IO. See hijacking.
|
||||
|
||||
## Additional environment specifications have approved to
|
||||
## standardized middleware APIs. None of these are required to
|
||||
## be implemented by the server.
|
||||
|
||||
## <tt>rack.session</tt>:: A hash like interface for storing
|
||||
## request session data.
|
||||
## The store must implement:
|
||||
if session = env[RACK_SESSION]
|
||||
## store(key, value) (aliased as []=);
|
||||
assert("session #{session.inspect} must respond to store and []=") {
|
||||
session.respond_to?(:store) && session.respond_to?(:[]=)
|
||||
}
|
||||
|
||||
## fetch(key, default = nil) (aliased as []);
|
||||
assert("session #{session.inspect} must respond to fetch and []") {
|
||||
session.respond_to?(:fetch) && session.respond_to?(:[])
|
||||
}
|
||||
|
||||
## delete(key);
|
||||
assert("session #{session.inspect} must respond to delete") {
|
||||
session.respond_to?(:delete)
|
||||
}
|
||||
|
||||
## clear;
|
||||
assert("session #{session.inspect} must respond to clear") {
|
||||
session.respond_to?(:clear)
|
||||
}
|
||||
|
||||
## to_hash (returning unfrozen Hash instance);
|
||||
assert("session #{session.inspect} must respond to to_hash and return unfrozen Hash instance") {
|
||||
session.respond_to?(:to_hash) && session.to_hash.kind_of?(Hash) && !session.to_hash.frozen?
|
||||
}
|
||||
end
|
||||
|
||||
## <tt>rack.logger</tt>:: A common object interface for logging messages.
|
||||
## The object must implement:
|
||||
if logger = env[RACK_LOGGER]
|
||||
## info(message, &block)
|
||||
assert("logger #{logger.inspect} must respond to info") {
|
||||
logger.respond_to?(:info)
|
||||
}
|
||||
|
||||
## debug(message, &block)
|
||||
assert("logger #{logger.inspect} must respond to debug") {
|
||||
logger.respond_to?(:debug)
|
||||
}
|
||||
|
||||
## warn(message, &block)
|
||||
assert("logger #{logger.inspect} must respond to warn") {
|
||||
logger.respond_to?(:warn)
|
||||
}
|
||||
|
||||
## error(message, &block)
|
||||
assert("logger #{logger.inspect} must respond to error") {
|
||||
logger.respond_to?(:error)
|
||||
}
|
||||
|
||||
## fatal(message, &block)
|
||||
assert("logger #{logger.inspect} must respond to fatal") {
|
||||
logger.respond_to?(:fatal)
|
||||
}
|
||||
end
|
||||
|
||||
## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
|
||||
if bufsize = env[RACK_MULTIPART_BUFFER_SIZE]
|
||||
assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
|
||||
bufsize.is_a?(Integer) && bufsize > 0
|
||||
}
|
||||
end
|
||||
|
||||
## <tt>rack.multipart.tempfile_factory</tt>:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
|
||||
if tempfile_factory = env[RACK_MULTIPART_TEMPFILE_FACTORY]
|
||||
assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
|
||||
env[RACK_MULTIPART_TEMPFILE_FACTORY] = lambda do |filename, content_type|
|
||||
io = tempfile_factory.call(filename, content_type)
|
||||
assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
|
||||
io
|
||||
end
|
||||
end
|
||||
|
||||
## The server or the application can store their own data in the
|
||||
## environment, too. The keys must contain at least one dot,
|
||||
## and should be prefixed uniquely. The prefix <tt>rack.</tt>
|
||||
## is reserved for use with the Rack core distribution and other
|
||||
## accepted specifications and must not be used otherwise.
|
||||
##
|
||||
|
||||
%w[REQUEST_METHOD SERVER_NAME QUERY_STRING
|
||||
rack.version rack.input rack.errors
|
||||
rack.multithread rack.multiprocess rack.run_once].each { |header|
|
||||
assert("env missing required key #{header}") { env.include? header }
|
||||
}
|
||||
|
||||
## The <tt>SERVER_PORT</tt> must be an Integer if set.
|
||||
assert("env[SERVER_PORT] is not an Integer") do
|
||||
server_port = env["SERVER_PORT"]
|
||||
server_port.nil? || (Integer(server_port) rescue false)
|
||||
end
|
||||
|
||||
## The <tt>SERVER_NAME</tt> must be a valid authority as defined by RFC7540.
|
||||
assert("#{env[SERVER_NAME]} must be a valid authority") do
|
||||
URI.parse("http://#{env[SERVER_NAME]}/") rescue false
|
||||
end
|
||||
|
||||
## The <tt>HTTP_HOST</tt> must be a valid authority as defined by RFC7540.
|
||||
assert("#{env[HTTP_HOST]} must be a valid authority") do
|
||||
URI.parse("http://#{env[HTTP_HOST]}/") rescue false
|
||||
end
|
||||
|
||||
## The environment must not contain the keys
|
||||
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
||||
## (use the versions without <tt>HTTP_</tt>).
|
||||
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
|
||||
assert("env contains #{header}, must use #{header[5, -1]}") {
|
||||
not env.include? header
|
||||
}
|
||||
}
|
||||
|
||||
## The CGI keys (named without a period) must have String values.
|
||||
## If the string values for CGI keys contain non-ASCII characters,
|
||||
## they should use ASCII-8BIT encoding.
|
||||
env.each { |key, value|
|
||||
next if key.include? "." # Skip extensions
|
||||
assert("env variable #{key} has non-string value #{value.inspect}") {
|
||||
value.kind_of? String
|
||||
}
|
||||
next if value.encoding == Encoding::ASCII_8BIT
|
||||
assert("env variable #{key} has value containing non-ASCII characters and has non-ASCII-8BIT encoding #{value.inspect} encoding: #{value.encoding}") {
|
||||
value.b !~ /[\x80-\xff]/n
|
||||
}
|
||||
}
|
||||
|
||||
## There are the following restrictions:
|
||||
|
||||
## * <tt>rack.version</tt> must be an array of Integers.
|
||||
assert("rack.version must be an Array, was #{env[RACK_VERSION].class}") {
|
||||
env[RACK_VERSION].kind_of? Array
|
||||
}
|
||||
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
|
||||
assert("rack.url_scheme unknown: #{env[RACK_URL_SCHEME].inspect}") {
|
||||
%w[http https].include?(env[RACK_URL_SCHEME])
|
||||
}
|
||||
|
||||
## * There must be a valid input stream in <tt>rack.input</tt>.
|
||||
check_input env[RACK_INPUT]
|
||||
## * There must be a valid error stream in <tt>rack.errors</tt>.
|
||||
check_error env[RACK_ERRORS]
|
||||
## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
|
||||
check_hijack env
|
||||
|
||||
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
|
||||
assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
|
||||
env[REQUEST_METHOD] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
|
||||
}
|
||||
|
||||
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
|
||||
assert("SCRIPT_NAME must start with /") {
|
||||
!env.include?(SCRIPT_NAME) ||
|
||||
env[SCRIPT_NAME] == "" ||
|
||||
env[SCRIPT_NAME] =~ /\A\//
|
||||
}
|
||||
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
|
||||
assert("PATH_INFO must start with /") {
|
||||
!env.include?(PATH_INFO) ||
|
||||
env[PATH_INFO] == "" ||
|
||||
env[PATH_INFO] =~ /\A\//
|
||||
}
|
||||
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
|
||||
assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
|
||||
!env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
|
||||
}
|
||||
|
||||
## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
|
||||
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
|
||||
## <tt>SCRIPT_NAME</tt> is empty.
|
||||
assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
|
||||
env[SCRIPT_NAME] || env[PATH_INFO]
|
||||
}
|
||||
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
|
||||
assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
|
||||
env[SCRIPT_NAME] != "/"
|
||||
}
|
||||
end
|
||||
|
||||
## === The Input Stream
|
||||
##
|
||||
## The input stream is an IO-like object which contains the raw HTTP
|
||||
## POST data.
|
||||
def check_input(input)
|
||||
## When applicable, its external encoding must be "ASCII-8BIT" and it
|
||||
## must be opened in binary mode, for Ruby 1.9 compatibility.
|
||||
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
|
||||
input.external_encoding == Encoding::ASCII_8BIT
|
||||
} if input.respond_to?(:external_encoding)
|
||||
assert("rack.input #{input} is not opened in binary mode") {
|
||||
input.binmode?
|
||||
} if input.respond_to?(:binmode?)
|
||||
|
||||
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
|
||||
[:gets, :each, :read, :rewind].each { |method|
|
||||
assert("rack.input #{input} does not respond to ##{method}") {
|
||||
input.respond_to? method
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
class InputWrapper
|
||||
include Assertion
|
||||
|
||||
def initialize(input)
|
||||
@input = input
|
||||
end
|
||||
|
||||
## * +gets+ must be called without arguments and return a string,
|
||||
## or +nil+ on EOF.
|
||||
def gets(*args)
|
||||
assert("rack.input#gets called with arguments") { args.size == 0 }
|
||||
v = @input.gets
|
||||
assert("rack.input#gets didn't return a String") {
|
||||
v.nil? or v.kind_of? String
|
||||
}
|
||||
v
|
||||
end
|
||||
|
||||
## * +read+ behaves like IO#read.
|
||||
## Its signature is <tt>read([length, [buffer]])</tt>.
|
||||
##
|
||||
## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
|
||||
## and +buffer+ must be a String and may not be nil.
|
||||
##
|
||||
## If +length+ is given and not nil, then this method reads at most
|
||||
## +length+ bytes from the input stream.
|
||||
##
|
||||
## If +length+ is not given or nil, then this method reads
|
||||
## all data until EOF.
|
||||
##
|
||||
## When EOF is reached, this method returns nil if +length+ is given
|
||||
## and not nil, or "" if +length+ is not given or is nil.
|
||||
##
|
||||
## If +buffer+ is given, then the read data will be placed
|
||||
## into +buffer+ instead of a newly created String object.
|
||||
def read(*args)
|
||||
assert("rack.input#read called with too many arguments") {
|
||||
args.size <= 2
|
||||
}
|
||||
if args.size >= 1
|
||||
assert("rack.input#read called with non-integer and non-nil length") {
|
||||
args.first.kind_of?(Integer) || args.first.nil?
|
||||
}
|
||||
assert("rack.input#read called with a negative length") {
|
||||
args.first.nil? || args.first >= 0
|
||||
}
|
||||
end
|
||||
if args.size >= 2
|
||||
assert("rack.input#read called with non-String buffer") {
|
||||
args[1].kind_of?(String)
|
||||
}
|
||||
end
|
||||
|
||||
v = @input.read(*args)
|
||||
|
||||
assert("rack.input#read didn't return nil or a String") {
|
||||
v.nil? or v.kind_of? String
|
||||
}
|
||||
if args[0].nil?
|
||||
assert("rack.input#read(nil) returned nil on EOF") {
|
||||
!v.nil?
|
||||
}
|
||||
end
|
||||
|
||||
v
|
||||
end
|
||||
|
||||
## * +each+ must be called without arguments and only yield Strings.
|
||||
def each(*args)
|
||||
assert("rack.input#each called with arguments") { args.size == 0 }
|
||||
@input.each { |line|
|
||||
assert("rack.input#each didn't yield a String") {
|
||||
line.kind_of? String
|
||||
}
|
||||
yield line
|
||||
}
|
||||
end
|
||||
|
||||
## * +rewind+ must be called without arguments. It rewinds the input
|
||||
## stream back to the beginning. It must not raise Errno::ESPIPE:
|
||||
## that is, it may not be a pipe or a socket. Therefore, handler
|
||||
## developers must buffer the input data into some rewindable object
|
||||
## if the underlying input stream is not rewindable.
|
||||
def rewind(*args)
|
||||
assert("rack.input#rewind called with arguments") { args.size == 0 }
|
||||
assert("rack.input#rewind raised Errno::ESPIPE") {
|
||||
begin
|
||||
@input.rewind
|
||||
true
|
||||
rescue Errno::ESPIPE
|
||||
false
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
## * +close+ must never be called on the input stream.
|
||||
def close(*args)
|
||||
assert("rack.input#close must not be called") { false }
|
||||
end
|
||||
end
|
||||
|
||||
## === The Error Stream
|
||||
def check_error(error)
|
||||
## The error stream must respond to +puts+, +write+ and +flush+.
|
||||
[:puts, :write, :flush].each { |method|
|
||||
assert("rack.error #{error} does not respond to ##{method}") {
|
||||
error.respond_to? method
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
class ErrorWrapper
|
||||
include Assertion
|
||||
|
||||
def initialize(error)
|
||||
@error = error
|
||||
end
|
||||
|
||||
## * +puts+ must be called with a single argument that responds to +to_s+.
|
||||
def puts(str)
|
||||
@error.puts str
|
||||
end
|
||||
|
||||
## * +write+ must be called with a single argument that is a String.
|
||||
def write(str)
|
||||
assert("rack.errors#write not called with a String") { str.kind_of? String }
|
||||
@error.write str
|
||||
end
|
||||
|
||||
## * +flush+ must be called without arguments and must be called
|
||||
## in order to make the error appear for sure.
|
||||
def flush
|
||||
@error.flush
|
||||
end
|
||||
|
||||
## * +close+ must never be called on the error stream.
|
||||
def close(*args)
|
||||
assert("rack.errors#close must not be called") { false }
|
||||
end
|
||||
end
|
||||
|
||||
class HijackWrapper
|
||||
include Assertion
|
||||
extend Forwardable
|
||||
|
||||
REQUIRED_METHODS = [
|
||||
:read, :write, :read_nonblock, :write_nonblock, :flush, :close,
|
||||
:close_read, :close_write, :closed?
|
||||
]
|
||||
|
||||
def_delegators :@io, *REQUIRED_METHODS
|
||||
|
||||
def initialize(io)
|
||||
@io = io
|
||||
REQUIRED_METHODS.each do |meth|
|
||||
assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
## === Hijacking
|
||||
#
|
||||
# AUTHORS: n.b. The trailing whitespace between paragraphs is important and
|
||||
# should not be removed. The whitespace creates paragraphs in the RDoc
|
||||
# output.
|
||||
#
|
||||
## ==== Request (before status)
|
||||
def check_hijack(env)
|
||||
if env[RACK_IS_HIJACK]
|
||||
## If rack.hijack? is true then rack.hijack must respond to #call.
|
||||
original_hijack = env[RACK_HIJACK]
|
||||
assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
|
||||
env[RACK_HIJACK] = proc do
|
||||
## rack.hijack must return the io that will also be assigned (or is
|
||||
## already present, in rack.hijack_io.
|
||||
io = original_hijack.call
|
||||
HijackWrapper.new(io)
|
||||
##
|
||||
## rack.hijack_io must respond to:
|
||||
## <tt>read, write, read_nonblock, write_nonblock, flush, close,
|
||||
## close_read, close_write, closed?</tt>
|
||||
##
|
||||
## The semantics of these IO methods must be a best effort match to
|
||||
## those of a normal ruby IO or Socket object, using standard
|
||||
## arguments and raising standard exceptions. Servers are encouraged
|
||||
## to simply pass on real IO objects, although it is recognized that
|
||||
## this approach is not directly compatible with SPDY and HTTP 2.0.
|
||||
##
|
||||
## IO provided in rack.hijack_io should preference the
|
||||
## IO::WaitReadable and IO::WaitWritable APIs wherever supported.
|
||||
##
|
||||
## There is a deliberate lack of full specification around
|
||||
## rack.hijack_io, as semantics will change from server to server.
|
||||
## Users are encouraged to utilize this API with a knowledge of their
|
||||
## server choice, and servers may extend the functionality of
|
||||
## hijack_io to provide additional features to users. The purpose of
|
||||
## rack.hijack is for Rack to "get out of the way", as such, Rack only
|
||||
## provides the minimum of specification and support.
|
||||
env[RACK_HIJACK_IO] = HijackWrapper.new(env[RACK_HIJACK_IO])
|
||||
io
|
||||
end
|
||||
else
|
||||
##
|
||||
## If rack.hijack? is false, then rack.hijack should not be set.
|
||||
assert("rack.hijack? is false, but rack.hijack is present") { env[RACK_HIJACK].nil? }
|
||||
##
|
||||
## If rack.hijack? is false, then rack.hijack_io should not be set.
|
||||
assert("rack.hijack? is false, but rack.hijack_io is present") { env[RACK_HIJACK_IO].nil? }
|
||||
end
|
||||
end
|
||||
|
||||
## ==== Response (after headers)
|
||||
## It is also possible to hijack a response after the status and headers
|
||||
## have been sent.
|
||||
def check_hijack_response(headers, env)
|
||||
|
||||
# this check uses headers like a hash, but the spec only requires
|
||||
# headers respond to #each
|
||||
headers = Rack::Utils::HeaderHash[headers]
|
||||
|
||||
## In order to do this, an application may set the special header
|
||||
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
|
||||
## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
|
||||
## protocol.
|
||||
##
|
||||
## After the headers have been sent, and this hijack callback has been
|
||||
## called, the application is now responsible for the remaining lifecycle
|
||||
## of the IO. The application is also responsible for maintaining HTTP
|
||||
## semantics. Of specific note, in almost all cases in the current SPEC,
|
||||
## applications will have wanted to specify the header Connection:close in
|
||||
## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
|
||||
## returning hijacked sockets to the web server. For that purpose, use the
|
||||
## body streaming API instead (progressively yielding strings via each).
|
||||
##
|
||||
## Servers must ignore the <tt>body</tt> part of the response tuple when
|
||||
## the <tt>rack.hijack</tt> response API is in use.
|
||||
|
||||
if env[RACK_IS_HIJACK] && headers[RACK_HIJACK]
|
||||
assert('rack.hijack header must respond to #call') {
|
||||
headers[RACK_HIJACK].respond_to? :call
|
||||
}
|
||||
original_hijack = headers[RACK_HIJACK]
|
||||
proc do |io|
|
||||
original_hijack.call HijackWrapper.new(io)
|
||||
end
|
||||
else
|
||||
##
|
||||
## The special response header <tt>rack.hijack</tt> must only be set
|
||||
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
|
||||
assert('rack.hijack header must not be present if server does not support hijacking') {
|
||||
headers[RACK_HIJACK].nil?
|
||||
}
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
## ==== Conventions
|
||||
## * Middleware should not use hijack unless it is handling the whole
|
||||
## response.
|
||||
## * Middleware may wrap the IO object for the response pattern.
|
||||
## * Middleware should not wrap the IO object for the request pattern. The
|
||||
## request pattern is intended to provide the hijacker with "raw tcp".
|
||||
|
||||
## == The Response
|
||||
|
||||
## === The Status
|
||||
def check_status(status)
|
||||
## This is an HTTP status. When parsed as integer (+to_i+), it must be
|
||||
## greater than or equal to 100.
|
||||
assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
|
||||
end
|
||||
|
||||
## === The Headers
|
||||
def check_headers(header)
|
||||
## The header must respond to +each+, and yield values of key and value.
|
||||
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
|
||||
header.respond_to? :each
|
||||
}
|
||||
|
||||
header.each { |key, value|
|
||||
## The header keys must be Strings.
|
||||
assert("header key must be a string, was #{key.class}") {
|
||||
key.kind_of? String
|
||||
}
|
||||
|
||||
## Special headers starting "rack." are for communicating with the
|
||||
## server, and must not be sent back to the client.
|
||||
next if key =~ /^rack\..+$/
|
||||
|
||||
## The header must not contain a +Status+ key.
|
||||
assert("header must not contain Status") { key.downcase != "status" }
|
||||
## The header must conform to RFC7230 token specification, i.e. cannot
|
||||
## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
|
||||
assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
|
||||
|
||||
## The values of the header must be Strings,
|
||||
assert("a header value must be a String, but the value of " +
|
||||
"'#{key}' is a #{value.class}") { value.kind_of? String }
|
||||
## consisting of lines (for multiple header values, e.g. multiple
|
||||
## <tt>Set-Cookie</tt> values) separated by "\\n".
|
||||
value.split("\n").each { |item|
|
||||
## The lines must not contain characters below 037.
|
||||
assert("invalid header value #{key}: #{item.inspect}") {
|
||||
item !~ /[\000-\037]/
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
## === The Content-Type
|
||||
def check_content_type(status, headers)
|
||||
headers.each { |key, value|
|
||||
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
|
||||
## 204 or 304.
|
||||
if key.downcase == "content-type"
|
||||
assert("Content-Type header found in #{status} response, not allowed") {
|
||||
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
||||
}
|
||||
return
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
## === The Content-Length
|
||||
def check_content_length(status, headers)
|
||||
headers.each { |key, value|
|
||||
if key.downcase == 'content-length'
|
||||
## There must not be a <tt>Content-Length</tt> header when the
|
||||
## +Status+ is 1xx, 204 or 304.
|
||||
assert("Content-Length header found in #{status} response, not allowed") {
|
||||
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key? status.to_i
|
||||
}
|
||||
@content_length = value
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def verify_content_length(bytes)
|
||||
if @head_request
|
||||
assert("Response body was given for HEAD request, but should be empty") {
|
||||
bytes == 0
|
||||
}
|
||||
elsif @content_length
|
||||
assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
|
||||
@content_length == bytes.to_s
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
## === The Body
|
||||
def each
|
||||
@closed = false
|
||||
bytes = 0
|
||||
|
||||
## The Body must respond to +each+
|
||||
assert("Response body must respond to each") do
|
||||
@body.respond_to?(:each)
|
||||
end
|
||||
|
||||
@body.each { |part|
|
||||
## and must only yield String values.
|
||||
assert("Body yielded non-string value #{part.inspect}") {
|
||||
part.kind_of? String
|
||||
}
|
||||
bytes += part.bytesize
|
||||
yield part
|
||||
}
|
||||
verify_content_length(bytes)
|
||||
|
||||
##
|
||||
## The Body itself should not be an instance of String, as this will
|
||||
## break in Ruby 1.9.
|
||||
##
|
||||
## If the Body responds to +close+, it will be called after iteration. If
|
||||
## the body is replaced by a middleware after action, the original body
|
||||
## must be closed first, if it responds to close.
|
||||
# XXX howto: assert("Body has not been closed") { @closed }
|
||||
|
||||
|
||||
##
|
||||
## If the Body responds to +to_path+, it must return a String
|
||||
## identifying the location of a file whose contents are identical
|
||||
## to that produced by calling +each+; this may be used by the
|
||||
## server as an alternative, possibly more efficient way to
|
||||
## transport the response.
|
||||
|
||||
if @body.respond_to?(:to_path)
|
||||
assert("The file identified by body.to_path does not exist") {
|
||||
::File.exist? @body.to_path
|
||||
}
|
||||
end
|
||||
|
||||
##
|
||||
## The Body commonly is an Array of Strings, the application
|
||||
## instance itself, or a File-like object.
|
||||
end
|
||||
|
||||
def close
|
||||
@closed = true
|
||||
@body.close if @body.respond_to?(:close)
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
## == Thanks
|
||||
## Some parts of this specification are adopted from PEP333: Python
|
||||
## Web Server Gateway Interface
|
||||
## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank
|
||||
## everyone involved in that effort.
|
||||
70
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/lobster.rb
vendored
Normal file
70
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/lobster.rb
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'zlib'
|
||||
|
||||
module Rack
|
||||
# Paste has a Pony, Rack has a Lobster!
|
||||
class Lobster
|
||||
LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2
|
||||
P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0
|
||||
t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ
|
||||
I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0])
|
||||
|
||||
LambdaLobster = lambda { |env|
|
||||
if env[QUERY_STRING].include?("flip")
|
||||
lobster = LobsterString.split("\n").
|
||||
map { |line| line.ljust(42).reverse }.
|
||||
join("\n")
|
||||
href = "?"
|
||||
else
|
||||
lobster = LobsterString
|
||||
href = "?flip"
|
||||
end
|
||||
|
||||
content = ["<title>Lobstericious!</title>",
|
||||
"<pre>", lobster, "</pre>",
|
||||
"<a href='#{href}'>flip!</a>"]
|
||||
length = content.inject(0) { |a, e| a + e.size }.to_s
|
||||
[200, { CONTENT_TYPE => "text/html", CONTENT_LENGTH => length }, content]
|
||||
}
|
||||
|
||||
def call(env)
|
||||
req = Request.new(env)
|
||||
if req.GET["flip"] == "left"
|
||||
lobster = LobsterString.split("\n").map do |line|
|
||||
line.ljust(42).reverse.
|
||||
gsub('\\', 'TEMP').
|
||||
gsub('/', '\\').
|
||||
gsub('TEMP', '/').
|
||||
gsub('{', '}').
|
||||
gsub('(', ')')
|
||||
end.join("\n")
|
||||
href = "?flip=right"
|
||||
elsif req.GET["flip"] == "crash"
|
||||
raise "Lobster crashed"
|
||||
else
|
||||
lobster = LobsterString
|
||||
href = "?flip=left"
|
||||
end
|
||||
|
||||
res = Response.new
|
||||
res.write "<title>Lobstericious!</title>"
|
||||
res.write "<pre>"
|
||||
res.write lobster
|
||||
res.write "</pre>"
|
||||
res.write "<p><a href='#{href}'>flip!</a></p>"
|
||||
res.write "<p><a href='?flip=crash'>crash!</a></p>"
|
||||
res.finish
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
# :nocov:
|
||||
require_relative '../rack'
|
||||
Rack::Server.start(
|
||||
app: Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), Port: 9292
|
||||
)
|
||||
# :nocov:
|
||||
end
|
||||
32
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/lock.rb
vendored
Normal file
32
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/lock.rb
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'thread'
|
||||
|
||||
module Rack
|
||||
# Rack::Lock locks every request inside a mutex, so that every request
|
||||
# will effectively be executed synchronously.
|
||||
class Lock
|
||||
def initialize(app, mutex = Mutex.new)
|
||||
@app, @mutex = app, mutex
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@mutex.lock
|
||||
@env = env
|
||||
@old_rack_multithread = env[RACK_MULTITHREAD]
|
||||
begin
|
||||
response = @app.call(env.merge!(RACK_MULTITHREAD => false))
|
||||
returned = response << BodyProxy.new(response.pop) { unlock }
|
||||
ensure
|
||||
unlock unless returned
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unlock
|
||||
@mutex.unlock
|
||||
@env[RACK_MULTITHREAD] = @old_rack_multithread
|
||||
end
|
||||
end
|
||||
end
|
||||
20
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/logger.rb
vendored
Normal file
20
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/logger.rb
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'logger'
|
||||
|
||||
module Rack
|
||||
# Sets up rack.logger to write to rack.errors stream
|
||||
class Logger
|
||||
def initialize(app, level = ::Logger::INFO)
|
||||
@app, @level = app, level
|
||||
end
|
||||
|
||||
def call(env)
|
||||
logger = ::Logger.new(env[RACK_ERRORS])
|
||||
logger.level = @level
|
||||
|
||||
env[RACK_LOGGER] = logger
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
43
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/media_type.rb
vendored
Normal file
43
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/media_type.rb
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Rack::MediaType parse media type and parameters out of content_type string
|
||||
|
||||
class MediaType
|
||||
SPLIT_PATTERN = %r{\s*[;,]\s*}
|
||||
|
||||
class << self
|
||||
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
||||
# without any media type parameters. e.g., when CONTENT_TYPE is
|
||||
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
||||
#
|
||||
# For more information on the use of media types in HTTP, see:
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
||||
def type(content_type)
|
||||
return nil unless content_type
|
||||
content_type.split(SPLIT_PATTERN, 2).first.tap &:downcase!
|
||||
end
|
||||
|
||||
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
||||
# an empty Hash if no CONTENT_TYPE or media-type parameters were
|
||||
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
|
||||
# this method responds with the following Hash:
|
||||
# { 'charset' => 'utf-8' }
|
||||
def params(content_type)
|
||||
return {} if content_type.nil?
|
||||
|
||||
content_type.split(SPLIT_PATTERN)[1..-1].each_with_object({}) do |s, hsh|
|
||||
k, v = s.split('=', 2)
|
||||
|
||||
hsh[k.tap(&:downcase!)] = strip_doublequotes(v)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def strip_doublequotes(str)
|
||||
(str.start_with?('"') && str.end_with?('"')) ? str[1..-2] : str
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
52
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/method_override.rb
vendored
Normal file
52
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/method_override.rb
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
class MethodOverride
|
||||
HTTP_METHODS = %w[GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK]
|
||||
|
||||
METHOD_OVERRIDE_PARAM_KEY = "_method"
|
||||
HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE"
|
||||
ALLOWED_METHODS = %w[POST]
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if allowed_methods.include?(env[REQUEST_METHOD])
|
||||
method = method_override(env)
|
||||
if HTTP_METHODS.include?(method)
|
||||
env[RACK_METHODOVERRIDE_ORIGINAL_METHOD] = env[REQUEST_METHOD]
|
||||
env[REQUEST_METHOD] = method
|
||||
end
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
def method_override(env)
|
||||
req = Request.new(env)
|
||||
method = method_override_param(req) ||
|
||||
env[HTTP_METHOD_OVERRIDE_HEADER]
|
||||
begin
|
||||
method.to_s.upcase
|
||||
rescue ArgumentError
|
||||
env[RACK_ERRORS].puts "Invalid string for method"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed_methods
|
||||
ALLOWED_METHODS
|
||||
end
|
||||
|
||||
def method_override_param(req)
|
||||
req.POST[METHOD_OVERRIDE_PARAM_KEY]
|
||||
rescue Utils::InvalidParameterError, Utils::ParameterTypeError
|
||||
req.get_header(RACK_ERRORS).puts "Invalid or incomplete POST params"
|
||||
rescue EOFError
|
||||
req.get_header(RACK_ERRORS).puts "Bad request content body"
|
||||
end
|
||||
end
|
||||
end
|
||||
685
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/mime.rb
vendored
Normal file
685
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/mime.rb
vendored
Normal file
@ -0,0 +1,685 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
module Mime
|
||||
# Returns String with mime type if found, otherwise use +fallback+.
|
||||
# +ext+ should be filename extension in the '.ext' format that
|
||||
# File.extname(file) returns.
|
||||
# +fallback+ may be any object
|
||||
#
|
||||
# Also see the documentation for MIME_TYPES
|
||||
#
|
||||
# Usage:
|
||||
# Rack::Mime.mime_type('.foo')
|
||||
#
|
||||
# This is a shortcut for:
|
||||
# Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
|
||||
|
||||
def mime_type(ext, fallback = 'application/octet-stream')
|
||||
MIME_TYPES.fetch(ext.to_s.downcase, fallback)
|
||||
end
|
||||
module_function :mime_type
|
||||
|
||||
# Returns true if the given value is a mime match for the given mime match
|
||||
# specification, false otherwise.
|
||||
#
|
||||
# Rack::Mime.match?('text/html', 'text/*') => true
|
||||
# Rack::Mime.match?('text/plain', '*') => true
|
||||
# Rack::Mime.match?('text/html', 'application/json') => false
|
||||
|
||||
def match?(value, matcher)
|
||||
v1, v2 = value.split('/', 2)
|
||||
m1, m2 = matcher.split('/', 2)
|
||||
|
||||
(m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
|
||||
end
|
||||
module_function :match?
|
||||
|
||||
# List of most common mime-types, selected various sources
|
||||
# according to their usefulness in a webserving scope for Ruby
|
||||
# users.
|
||||
#
|
||||
# To amend this list with your local mime.types list you can use:
|
||||
#
|
||||
# require 'webrick/httputils'
|
||||
# list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
|
||||
# Rack::Mime::MIME_TYPES.merge!(list)
|
||||
#
|
||||
# N.B. On Ubuntu the mime.types file does not include the leading period, so
|
||||
# users may need to modify the data before merging into the hash.
|
||||
|
||||
MIME_TYPES = {
|
||||
".123" => "application/vnd.lotus-1-2-3",
|
||||
".3dml" => "text/vnd.in3d.3dml",
|
||||
".3g2" => "video/3gpp2",
|
||||
".3gp" => "video/3gpp",
|
||||
".a" => "application/octet-stream",
|
||||
".acc" => "application/vnd.americandynamics.acc",
|
||||
".ace" => "application/x-ace-compressed",
|
||||
".acu" => "application/vnd.acucobol",
|
||||
".aep" => "application/vnd.audiograph",
|
||||
".afp" => "application/vnd.ibm.modcap",
|
||||
".ai" => "application/postscript",
|
||||
".aif" => "audio/x-aiff",
|
||||
".aiff" => "audio/x-aiff",
|
||||
".ami" => "application/vnd.amiga.ami",
|
||||
".appcache" => "text/cache-manifest",
|
||||
".apr" => "application/vnd.lotus-approach",
|
||||
".asc" => "application/pgp-signature",
|
||||
".asf" => "video/x-ms-asf",
|
||||
".asm" => "text/x-asm",
|
||||
".aso" => "application/vnd.accpac.simply.aso",
|
||||
".asx" => "video/x-ms-asf",
|
||||
".atc" => "application/vnd.acucorp",
|
||||
".atom" => "application/atom+xml",
|
||||
".atomcat" => "application/atomcat+xml",
|
||||
".atomsvc" => "application/atomsvc+xml",
|
||||
".atx" => "application/vnd.antix.game-component",
|
||||
".au" => "audio/basic",
|
||||
".avi" => "video/x-msvideo",
|
||||
".bat" => "application/x-msdownload",
|
||||
".bcpio" => "application/x-bcpio",
|
||||
".bdm" => "application/vnd.syncml.dm+wbxml",
|
||||
".bh2" => "application/vnd.fujitsu.oasysprs",
|
||||
".bin" => "application/octet-stream",
|
||||
".bmi" => "application/vnd.bmi",
|
||||
".bmp" => "image/bmp",
|
||||
".box" => "application/vnd.previewsystems.box",
|
||||
".btif" => "image/prs.btif",
|
||||
".bz" => "application/x-bzip",
|
||||
".bz2" => "application/x-bzip2",
|
||||
".c" => "text/x-c",
|
||||
".c4g" => "application/vnd.clonk.c4group",
|
||||
".cab" => "application/vnd.ms-cab-compressed",
|
||||
".cc" => "text/x-c",
|
||||
".ccxml" => "application/ccxml+xml",
|
||||
".cdbcmsg" => "application/vnd.contact.cmsg",
|
||||
".cdkey" => "application/vnd.mediastation.cdkey",
|
||||
".cdx" => "chemical/x-cdx",
|
||||
".cdxml" => "application/vnd.chemdraw+xml",
|
||||
".cdy" => "application/vnd.cinderella",
|
||||
".cer" => "application/pkix-cert",
|
||||
".cgm" => "image/cgm",
|
||||
".chat" => "application/x-chat",
|
||||
".chm" => "application/vnd.ms-htmlhelp",
|
||||
".chrt" => "application/vnd.kde.kchart",
|
||||
".cif" => "chemical/x-cif",
|
||||
".cii" => "application/vnd.anser-web-certificate-issue-initiation",
|
||||
".cil" => "application/vnd.ms-artgalry",
|
||||
".cla" => "application/vnd.claymore",
|
||||
".class" => "application/octet-stream",
|
||||
".clkk" => "application/vnd.crick.clicker.keyboard",
|
||||
".clkp" => "application/vnd.crick.clicker.palette",
|
||||
".clkt" => "application/vnd.crick.clicker.template",
|
||||
".clkw" => "application/vnd.crick.clicker.wordbank",
|
||||
".clkx" => "application/vnd.crick.clicker",
|
||||
".clp" => "application/x-msclip",
|
||||
".cmc" => "application/vnd.cosmocaller",
|
||||
".cmdf" => "chemical/x-cmdf",
|
||||
".cml" => "chemical/x-cml",
|
||||
".cmp" => "application/vnd.yellowriver-custom-menu",
|
||||
".cmx" => "image/x-cmx",
|
||||
".com" => "application/x-msdownload",
|
||||
".conf" => "text/plain",
|
||||
".cpio" => "application/x-cpio",
|
||||
".cpp" => "text/x-c",
|
||||
".cpt" => "application/mac-compactpro",
|
||||
".crd" => "application/x-mscardfile",
|
||||
".crl" => "application/pkix-crl",
|
||||
".crt" => "application/x-x509-ca-cert",
|
||||
".csh" => "application/x-csh",
|
||||
".csml" => "chemical/x-csml",
|
||||
".csp" => "application/vnd.commonspace",
|
||||
".css" => "text/css",
|
||||
".csv" => "text/csv",
|
||||
".curl" => "application/vnd.curl",
|
||||
".cww" => "application/prs.cww",
|
||||
".cxx" => "text/x-c",
|
||||
".daf" => "application/vnd.mobius.daf",
|
||||
".davmount" => "application/davmount+xml",
|
||||
".dcr" => "application/x-director",
|
||||
".dd2" => "application/vnd.oma.dd2+xml",
|
||||
".ddd" => "application/vnd.fujixerox.ddd",
|
||||
".deb" => "application/x-debian-package",
|
||||
".der" => "application/x-x509-ca-cert",
|
||||
".dfac" => "application/vnd.dreamfactory",
|
||||
".diff" => "text/x-diff",
|
||||
".dis" => "application/vnd.mobius.dis",
|
||||
".djv" => "image/vnd.djvu",
|
||||
".djvu" => "image/vnd.djvu",
|
||||
".dll" => "application/x-msdownload",
|
||||
".dmg" => "application/octet-stream",
|
||||
".dna" => "application/vnd.dna",
|
||||
".doc" => "application/msword",
|
||||
".docm" => "application/vnd.ms-word.document.macroEnabled.12",
|
||||
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
".dot" => "application/msword",
|
||||
".dotm" => "application/vnd.ms-word.template.macroEnabled.12",
|
||||
".dotx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
||||
".dp" => "application/vnd.osgi.dp",
|
||||
".dpg" => "application/vnd.dpgraph",
|
||||
".dsc" => "text/prs.lines.tag",
|
||||
".dtd" => "application/xml-dtd",
|
||||
".dts" => "audio/vnd.dts",
|
||||
".dtshd" => "audio/vnd.dts.hd",
|
||||
".dv" => "video/x-dv",
|
||||
".dvi" => "application/x-dvi",
|
||||
".dwf" => "model/vnd.dwf",
|
||||
".dwg" => "image/vnd.dwg",
|
||||
".dxf" => "image/vnd.dxf",
|
||||
".dxp" => "application/vnd.spotfire.dxp",
|
||||
".ear" => "application/java-archive",
|
||||
".ecelp4800" => "audio/vnd.nuera.ecelp4800",
|
||||
".ecelp7470" => "audio/vnd.nuera.ecelp7470",
|
||||
".ecelp9600" => "audio/vnd.nuera.ecelp9600",
|
||||
".ecma" => "application/ecmascript",
|
||||
".edm" => "application/vnd.novadigm.edm",
|
||||
".edx" => "application/vnd.novadigm.edx",
|
||||
".efif" => "application/vnd.picsel",
|
||||
".ei6" => "application/vnd.pg.osasli",
|
||||
".eml" => "message/rfc822",
|
||||
".eol" => "audio/vnd.digital-winds",
|
||||
".eot" => "application/vnd.ms-fontobject",
|
||||
".eps" => "application/postscript",
|
||||
".es3" => "application/vnd.eszigno3+xml",
|
||||
".esf" => "application/vnd.epson.esf",
|
||||
".etx" => "text/x-setext",
|
||||
".exe" => "application/x-msdownload",
|
||||
".ext" => "application/vnd.novadigm.ext",
|
||||
".ez" => "application/andrew-inset",
|
||||
".ez2" => "application/vnd.ezpix-album",
|
||||
".ez3" => "application/vnd.ezpix-package",
|
||||
".f" => "text/x-fortran",
|
||||
".f77" => "text/x-fortran",
|
||||
".f90" => "text/x-fortran",
|
||||
".fbs" => "image/vnd.fastbidsheet",
|
||||
".fdf" => "application/vnd.fdf",
|
||||
".fe_launch" => "application/vnd.denovo.fcselayout-link",
|
||||
".fg5" => "application/vnd.fujitsu.oasysgp",
|
||||
".fli" => "video/x-fli",
|
||||
".flo" => "application/vnd.micrografx.flo",
|
||||
".flv" => "video/x-flv",
|
||||
".flw" => "application/vnd.kde.kivio",
|
||||
".flx" => "text/vnd.fmi.flexstor",
|
||||
".fly" => "text/vnd.fly",
|
||||
".fm" => "application/vnd.framemaker",
|
||||
".fnc" => "application/vnd.frogans.fnc",
|
||||
".for" => "text/x-fortran",
|
||||
".fpx" => "image/vnd.fpx",
|
||||
".fsc" => "application/vnd.fsc.weblaunch",
|
||||
".fst" => "image/vnd.fst",
|
||||
".ftc" => "application/vnd.fluxtime.clip",
|
||||
".fti" => "application/vnd.anser-web-funds-transfer-initiation",
|
||||
".fvt" => "video/vnd.fvt",
|
||||
".fzs" => "application/vnd.fuzzysheet",
|
||||
".g3" => "image/g3fax",
|
||||
".gac" => "application/vnd.groove-account",
|
||||
".gdl" => "model/vnd.gdl",
|
||||
".gem" => "application/octet-stream",
|
||||
".gemspec" => "text/x-script.ruby",
|
||||
".ghf" => "application/vnd.groove-help",
|
||||
".gif" => "image/gif",
|
||||
".gim" => "application/vnd.groove-identity-message",
|
||||
".gmx" => "application/vnd.gmx",
|
||||
".gph" => "application/vnd.flographit",
|
||||
".gqf" => "application/vnd.grafeq",
|
||||
".gram" => "application/srgs",
|
||||
".grv" => "application/vnd.groove-injector",
|
||||
".grxml" => "application/srgs+xml",
|
||||
".gtar" => "application/x-gtar",
|
||||
".gtm" => "application/vnd.groove-tool-message",
|
||||
".gtw" => "model/vnd.gtw",
|
||||
".gv" => "text/vnd.graphviz",
|
||||
".gz" => "application/x-gzip",
|
||||
".h" => "text/x-c",
|
||||
".h261" => "video/h261",
|
||||
".h263" => "video/h263",
|
||||
".h264" => "video/h264",
|
||||
".hbci" => "application/vnd.hbci",
|
||||
".hdf" => "application/x-hdf",
|
||||
".hh" => "text/x-c",
|
||||
".hlp" => "application/winhlp",
|
||||
".hpgl" => "application/vnd.hp-hpgl",
|
||||
".hpid" => "application/vnd.hp-hpid",
|
||||
".hps" => "application/vnd.hp-hps",
|
||||
".hqx" => "application/mac-binhex40",
|
||||
".htc" => "text/x-component",
|
||||
".htke" => "application/vnd.kenameaapp",
|
||||
".htm" => "text/html",
|
||||
".html" => "text/html",
|
||||
".hvd" => "application/vnd.yamaha.hv-dic",
|
||||
".hvp" => "application/vnd.yamaha.hv-voice",
|
||||
".hvs" => "application/vnd.yamaha.hv-script",
|
||||
".icc" => "application/vnd.iccprofile",
|
||||
".ice" => "x-conference/x-cooltalk",
|
||||
".ico" => "image/vnd.microsoft.icon",
|
||||
".ics" => "text/calendar",
|
||||
".ief" => "image/ief",
|
||||
".ifb" => "text/calendar",
|
||||
".ifm" => "application/vnd.shana.informed.formdata",
|
||||
".igl" => "application/vnd.igloader",
|
||||
".igs" => "model/iges",
|
||||
".igx" => "application/vnd.micrografx.igx",
|
||||
".iif" => "application/vnd.shana.informed.interchange",
|
||||
".imp" => "application/vnd.accpac.simply.imp",
|
||||
".ims" => "application/vnd.ms-ims",
|
||||
".ipk" => "application/vnd.shana.informed.package",
|
||||
".irm" => "application/vnd.ibm.rights-management",
|
||||
".irp" => "application/vnd.irepository.package+xml",
|
||||
".iso" => "application/octet-stream",
|
||||
".itp" => "application/vnd.shana.informed.formtemplate",
|
||||
".ivp" => "application/vnd.immervision-ivp",
|
||||
".ivu" => "application/vnd.immervision-ivu",
|
||||
".jad" => "text/vnd.sun.j2me.app-descriptor",
|
||||
".jam" => "application/vnd.jam",
|
||||
".jar" => "application/java-archive",
|
||||
".java" => "text/x-java-source",
|
||||
".jisp" => "application/vnd.jisp",
|
||||
".jlt" => "application/vnd.hp-jlyt",
|
||||
".jnlp" => "application/x-java-jnlp-file",
|
||||
".joda" => "application/vnd.joost.joda-archive",
|
||||
".jp2" => "image/jp2",
|
||||
".jpeg" => "image/jpeg",
|
||||
".jpg" => "image/jpeg",
|
||||
".jpgv" => "video/jpeg",
|
||||
".jpm" => "video/jpm",
|
||||
".js" => "application/javascript",
|
||||
".json" => "application/json",
|
||||
".karbon" => "application/vnd.kde.karbon",
|
||||
".kfo" => "application/vnd.kde.kformula",
|
||||
".kia" => "application/vnd.kidspiration",
|
||||
".kml" => "application/vnd.google-earth.kml+xml",
|
||||
".kmz" => "application/vnd.google-earth.kmz",
|
||||
".kne" => "application/vnd.kinar",
|
||||
".kon" => "application/vnd.kde.kontour",
|
||||
".kpr" => "application/vnd.kde.kpresenter",
|
||||
".ksp" => "application/vnd.kde.kspread",
|
||||
".ktz" => "application/vnd.kahootz",
|
||||
".kwd" => "application/vnd.kde.kword",
|
||||
".latex" => "application/x-latex",
|
||||
".lbd" => "application/vnd.llamagraphics.life-balance.desktop",
|
||||
".lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml",
|
||||
".les" => "application/vnd.hhe.lesson-player",
|
||||
".link66" => "application/vnd.route66.link66+xml",
|
||||
".log" => "text/plain",
|
||||
".lostxml" => "application/lost+xml",
|
||||
".lrm" => "application/vnd.ms-lrm",
|
||||
".ltf" => "application/vnd.frogans.ltf",
|
||||
".lvp" => "audio/vnd.lucent.voice",
|
||||
".lwp" => "application/vnd.lotus-wordpro",
|
||||
".m3u" => "audio/x-mpegurl",
|
||||
".m3u8" => "application/x-mpegurl",
|
||||
".m4a" => "audio/mp4a-latm",
|
||||
".m4v" => "video/mp4",
|
||||
".ma" => "application/mathematica",
|
||||
".mag" => "application/vnd.ecowin.chart",
|
||||
".man" => "text/troff",
|
||||
".manifest" => "text/cache-manifest",
|
||||
".mathml" => "application/mathml+xml",
|
||||
".mbk" => "application/vnd.mobius.mbk",
|
||||
".mbox" => "application/mbox",
|
||||
".mc1" => "application/vnd.medcalcdata",
|
||||
".mcd" => "application/vnd.mcd",
|
||||
".mdb" => "application/x-msaccess",
|
||||
".mdi" => "image/vnd.ms-modi",
|
||||
".mdoc" => "text/troff",
|
||||
".me" => "text/troff",
|
||||
".mfm" => "application/vnd.mfmp",
|
||||
".mgz" => "application/vnd.proteus.magazine",
|
||||
".mid" => "audio/midi",
|
||||
".midi" => "audio/midi",
|
||||
".mif" => "application/vnd.mif",
|
||||
".mime" => "message/rfc822",
|
||||
".mj2" => "video/mj2",
|
||||
".mlp" => "application/vnd.dolby.mlp",
|
||||
".mmd" => "application/vnd.chipnuts.karaoke-mmd",
|
||||
".mmf" => "application/vnd.smaf",
|
||||
".mml" => "application/mathml+xml",
|
||||
".mmr" => "image/vnd.fujixerox.edmics-mmr",
|
||||
".mng" => "video/x-mng",
|
||||
".mny" => "application/x-msmoney",
|
||||
".mov" => "video/quicktime",
|
||||
".movie" => "video/x-sgi-movie",
|
||||
".mp3" => "audio/mpeg",
|
||||
".mp4" => "video/mp4",
|
||||
".mp4a" => "audio/mp4",
|
||||
".mp4s" => "application/mp4",
|
||||
".mp4v" => "video/mp4",
|
||||
".mpc" => "application/vnd.mophun.certificate",
|
||||
".mpd" => "application/dash+xml",
|
||||
".mpeg" => "video/mpeg",
|
||||
".mpg" => "video/mpeg",
|
||||
".mpga" => "audio/mpeg",
|
||||
".mpkg" => "application/vnd.apple.installer+xml",
|
||||
".mpm" => "application/vnd.blueice.multipass",
|
||||
".mpn" => "application/vnd.mophun.application",
|
||||
".mpp" => "application/vnd.ms-project",
|
||||
".mpy" => "application/vnd.ibm.minipay",
|
||||
".mqy" => "application/vnd.mobius.mqy",
|
||||
".mrc" => "application/marc",
|
||||
".ms" => "text/troff",
|
||||
".mscml" => "application/mediaservercontrol+xml",
|
||||
".mseq" => "application/vnd.mseq",
|
||||
".msf" => "application/vnd.epson.msf",
|
||||
".msh" => "model/mesh",
|
||||
".msi" => "application/x-msdownload",
|
||||
".msl" => "application/vnd.mobius.msl",
|
||||
".msty" => "application/vnd.muvee.style",
|
||||
".mts" => "model/vnd.mts",
|
||||
".mus" => "application/vnd.musician",
|
||||
".mvb" => "application/x-msmediaview",
|
||||
".mwf" => "application/vnd.mfer",
|
||||
".mxf" => "application/mxf",
|
||||
".mxl" => "application/vnd.recordare.musicxml",
|
||||
".mxml" => "application/xv+xml",
|
||||
".mxs" => "application/vnd.triscape.mxs",
|
||||
".mxu" => "video/vnd.mpegurl",
|
||||
".n" => "application/vnd.nokia.n-gage.symbian.install",
|
||||
".nc" => "application/x-netcdf",
|
||||
".ngdat" => "application/vnd.nokia.n-gage.data",
|
||||
".nlu" => "application/vnd.neurolanguage.nlu",
|
||||
".nml" => "application/vnd.enliven",
|
||||
".nnd" => "application/vnd.noblenet-directory",
|
||||
".nns" => "application/vnd.noblenet-sealer",
|
||||
".nnw" => "application/vnd.noblenet-web",
|
||||
".npx" => "image/vnd.net-fpx",
|
||||
".nsf" => "application/vnd.lotus-notes",
|
||||
".oa2" => "application/vnd.fujitsu.oasys2",
|
||||
".oa3" => "application/vnd.fujitsu.oasys3",
|
||||
".oas" => "application/vnd.fujitsu.oasys",
|
||||
".obd" => "application/x-msbinder",
|
||||
".oda" => "application/oda",
|
||||
".odc" => "application/vnd.oasis.opendocument.chart",
|
||||
".odf" => "application/vnd.oasis.opendocument.formula",
|
||||
".odg" => "application/vnd.oasis.opendocument.graphics",
|
||||
".odi" => "application/vnd.oasis.opendocument.image",
|
||||
".odp" => "application/vnd.oasis.opendocument.presentation",
|
||||
".ods" => "application/vnd.oasis.opendocument.spreadsheet",
|
||||
".odt" => "application/vnd.oasis.opendocument.text",
|
||||
".oga" => "audio/ogg",
|
||||
".ogg" => "application/ogg",
|
||||
".ogv" => "video/ogg",
|
||||
".ogx" => "application/ogg",
|
||||
".org" => "application/vnd.lotus-organizer",
|
||||
".otc" => "application/vnd.oasis.opendocument.chart-template",
|
||||
".otf" => "application/vnd.oasis.opendocument.formula-template",
|
||||
".otg" => "application/vnd.oasis.opendocument.graphics-template",
|
||||
".oth" => "application/vnd.oasis.opendocument.text-web",
|
||||
".oti" => "application/vnd.oasis.opendocument.image-template",
|
||||
".otm" => "application/vnd.oasis.opendocument.text-master",
|
||||
".ots" => "application/vnd.oasis.opendocument.spreadsheet-template",
|
||||
".ott" => "application/vnd.oasis.opendocument.text-template",
|
||||
".oxt" => "application/vnd.openofficeorg.extension",
|
||||
".p" => "text/x-pascal",
|
||||
".p10" => "application/pkcs10",
|
||||
".p12" => "application/x-pkcs12",
|
||||
".p7b" => "application/x-pkcs7-certificates",
|
||||
".p7m" => "application/pkcs7-mime",
|
||||
".p7r" => "application/x-pkcs7-certreqresp",
|
||||
".p7s" => "application/pkcs7-signature",
|
||||
".pas" => "text/x-pascal",
|
||||
".pbd" => "application/vnd.powerbuilder6",
|
||||
".pbm" => "image/x-portable-bitmap",
|
||||
".pcl" => "application/vnd.hp-pcl",
|
||||
".pclxl" => "application/vnd.hp-pclxl",
|
||||
".pcx" => "image/x-pcx",
|
||||
".pdb" => "chemical/x-pdb",
|
||||
".pdf" => "application/pdf",
|
||||
".pem" => "application/x-x509-ca-cert",
|
||||
".pfr" => "application/font-tdpfr",
|
||||
".pgm" => "image/x-portable-graymap",
|
||||
".pgn" => "application/x-chess-pgn",
|
||||
".pgp" => "application/pgp-encrypted",
|
||||
".pic" => "image/x-pict",
|
||||
".pict" => "image/pict",
|
||||
".pkg" => "application/octet-stream",
|
||||
".pki" => "application/pkixcmp",
|
||||
".pkipath" => "application/pkix-pkipath",
|
||||
".pl" => "text/x-script.perl",
|
||||
".plb" => "application/vnd.3gpp.pic-bw-large",
|
||||
".plc" => "application/vnd.mobius.plc",
|
||||
".plf" => "application/vnd.pocketlearn",
|
||||
".pls" => "application/pls+xml",
|
||||
".pm" => "text/x-script.perl-module",
|
||||
".pml" => "application/vnd.ctc-posml",
|
||||
".png" => "image/png",
|
||||
".pnm" => "image/x-portable-anymap",
|
||||
".pntg" => "image/x-macpaint",
|
||||
".portpkg" => "application/vnd.macports.portpkg",
|
||||
".pot" => "application/vnd.ms-powerpoint",
|
||||
".potm" => "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||
".potx" => "application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
".ppa" => "application/vnd.ms-powerpoint",
|
||||
".ppam" => "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
||||
".ppd" => "application/vnd.cups-ppd",
|
||||
".ppm" => "image/x-portable-pixmap",
|
||||
".pps" => "application/vnd.ms-powerpoint",
|
||||
".ppsm" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
||||
".ppsx" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
".ppt" => "application/vnd.ms-powerpoint",
|
||||
".pptm" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||
".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
".prc" => "application/vnd.palm",
|
||||
".pre" => "application/vnd.lotus-freelance",
|
||||
".prf" => "application/pics-rules",
|
||||
".ps" => "application/postscript",
|
||||
".psb" => "application/vnd.3gpp.pic-bw-small",
|
||||
".psd" => "image/vnd.adobe.photoshop",
|
||||
".ptid" => "application/vnd.pvi.ptid1",
|
||||
".pub" => "application/x-mspublisher",
|
||||
".pvb" => "application/vnd.3gpp.pic-bw-var",
|
||||
".pwn" => "application/vnd.3m.post-it-notes",
|
||||
".py" => "text/x-script.python",
|
||||
".pya" => "audio/vnd.ms-playready.media.pya",
|
||||
".pyv" => "video/vnd.ms-playready.media.pyv",
|
||||
".qam" => "application/vnd.epson.quickanime",
|
||||
".qbo" => "application/vnd.intu.qbo",
|
||||
".qfx" => "application/vnd.intu.qfx",
|
||||
".qps" => "application/vnd.publishare-delta-tree",
|
||||
".qt" => "video/quicktime",
|
||||
".qtif" => "image/x-quicktime",
|
||||
".qxd" => "application/vnd.quark.quarkxpress",
|
||||
".ra" => "audio/x-pn-realaudio",
|
||||
".rake" => "text/x-script.ruby",
|
||||
".ram" => "audio/x-pn-realaudio",
|
||||
".rar" => "application/x-rar-compressed",
|
||||
".ras" => "image/x-cmu-raster",
|
||||
".rb" => "text/x-script.ruby",
|
||||
".rcprofile" => "application/vnd.ipunplugged.rcprofile",
|
||||
".rdf" => "application/rdf+xml",
|
||||
".rdz" => "application/vnd.data-vision.rdz",
|
||||
".rep" => "application/vnd.businessobjects",
|
||||
".rgb" => "image/x-rgb",
|
||||
".rif" => "application/reginfo+xml",
|
||||
".rl" => "application/resource-lists+xml",
|
||||
".rlc" => "image/vnd.fujixerox.edmics-rlc",
|
||||
".rld" => "application/resource-lists-diff+xml",
|
||||
".rm" => "application/vnd.rn-realmedia",
|
||||
".rmp" => "audio/x-pn-realaudio-plugin",
|
||||
".rms" => "application/vnd.jcp.javame.midlet-rms",
|
||||
".rnc" => "application/relax-ng-compact-syntax",
|
||||
".roff" => "text/troff",
|
||||
".rpm" => "application/x-redhat-package-manager",
|
||||
".rpss" => "application/vnd.nokia.radio-presets",
|
||||
".rpst" => "application/vnd.nokia.radio-preset",
|
||||
".rq" => "application/sparql-query",
|
||||
".rs" => "application/rls-services+xml",
|
||||
".rsd" => "application/rsd+xml",
|
||||
".rss" => "application/rss+xml",
|
||||
".rtf" => "application/rtf",
|
||||
".rtx" => "text/richtext",
|
||||
".ru" => "text/x-script.ruby",
|
||||
".s" => "text/x-asm",
|
||||
".saf" => "application/vnd.yamaha.smaf-audio",
|
||||
".sbml" => "application/sbml+xml",
|
||||
".sc" => "application/vnd.ibm.secure-container",
|
||||
".scd" => "application/x-msschedule",
|
||||
".scm" => "application/vnd.lotus-screencam",
|
||||
".scq" => "application/scvp-cv-request",
|
||||
".scs" => "application/scvp-cv-response",
|
||||
".sdkm" => "application/vnd.solent.sdkm+xml",
|
||||
".sdp" => "application/sdp",
|
||||
".see" => "application/vnd.seemail",
|
||||
".sema" => "application/vnd.sema",
|
||||
".semd" => "application/vnd.semd",
|
||||
".semf" => "application/vnd.semf",
|
||||
".setpay" => "application/set-payment-initiation",
|
||||
".setreg" => "application/set-registration-initiation",
|
||||
".sfd" => "application/vnd.hydrostatix.sof-data",
|
||||
".sfs" => "application/vnd.spotfire.sfs",
|
||||
".sgm" => "text/sgml",
|
||||
".sgml" => "text/sgml",
|
||||
".sh" => "application/x-sh",
|
||||
".shar" => "application/x-shar",
|
||||
".shf" => "application/shf+xml",
|
||||
".sig" => "application/pgp-signature",
|
||||
".sit" => "application/x-stuffit",
|
||||
".sitx" => "application/x-stuffitx",
|
||||
".skp" => "application/vnd.koan",
|
||||
".slt" => "application/vnd.epson.salt",
|
||||
".smi" => "application/smil+xml",
|
||||
".snd" => "audio/basic",
|
||||
".so" => "application/octet-stream",
|
||||
".spf" => "application/vnd.yamaha.smaf-phrase",
|
||||
".spl" => "application/x-futuresplash",
|
||||
".spot" => "text/vnd.in3d.spot",
|
||||
".spp" => "application/scvp-vp-response",
|
||||
".spq" => "application/scvp-vp-request",
|
||||
".src" => "application/x-wais-source",
|
||||
".srt" => "text/srt",
|
||||
".srx" => "application/sparql-results+xml",
|
||||
".sse" => "application/vnd.kodak-descriptor",
|
||||
".ssf" => "application/vnd.epson.ssf",
|
||||
".ssml" => "application/ssml+xml",
|
||||
".stf" => "application/vnd.wt.stf",
|
||||
".stk" => "application/hyperstudio",
|
||||
".str" => "application/vnd.pg.format",
|
||||
".sus" => "application/vnd.sus-calendar",
|
||||
".sv4cpio" => "application/x-sv4cpio",
|
||||
".sv4crc" => "application/x-sv4crc",
|
||||
".svd" => "application/vnd.svd",
|
||||
".svg" => "image/svg+xml",
|
||||
".svgz" => "image/svg+xml",
|
||||
".swf" => "application/x-shockwave-flash",
|
||||
".swi" => "application/vnd.arastra.swi",
|
||||
".t" => "text/troff",
|
||||
".tao" => "application/vnd.tao.intent-module-archive",
|
||||
".tar" => "application/x-tar",
|
||||
".tbz" => "application/x-bzip-compressed-tar",
|
||||
".tcap" => "application/vnd.3gpp2.tcap",
|
||||
".tcl" => "application/x-tcl",
|
||||
".tex" => "application/x-tex",
|
||||
".texi" => "application/x-texinfo",
|
||||
".texinfo" => "application/x-texinfo",
|
||||
".text" => "text/plain",
|
||||
".tif" => "image/tiff",
|
||||
".tiff" => "image/tiff",
|
||||
".tmo" => "application/vnd.tmobile-livetv",
|
||||
".torrent" => "application/x-bittorrent",
|
||||
".tpl" => "application/vnd.groove-tool-template",
|
||||
".tpt" => "application/vnd.trid.tpt",
|
||||
".tr" => "text/troff",
|
||||
".tra" => "application/vnd.trueapp",
|
||||
".trm" => "application/x-msterminal",
|
||||
".ts" => "video/mp2t",
|
||||
".tsv" => "text/tab-separated-values",
|
||||
".ttf" => "application/octet-stream",
|
||||
".twd" => "application/vnd.simtech-mindmapper",
|
||||
".txd" => "application/vnd.genomatix.tuxedo",
|
||||
".txf" => "application/vnd.mobius.txf",
|
||||
".txt" => "text/plain",
|
||||
".ufd" => "application/vnd.ufdl",
|
||||
".umj" => "application/vnd.umajin",
|
||||
".unityweb" => "application/vnd.unity",
|
||||
".uoml" => "application/vnd.uoml+xml",
|
||||
".uri" => "text/uri-list",
|
||||
".ustar" => "application/x-ustar",
|
||||
".utz" => "application/vnd.uiq.theme",
|
||||
".uu" => "text/x-uuencode",
|
||||
".vcd" => "application/x-cdlink",
|
||||
".vcf" => "text/x-vcard",
|
||||
".vcg" => "application/vnd.groove-vcard",
|
||||
".vcs" => "text/x-vcalendar",
|
||||
".vcx" => "application/vnd.vcx",
|
||||
".vis" => "application/vnd.visionary",
|
||||
".viv" => "video/vnd.vivo",
|
||||
".vrml" => "model/vrml",
|
||||
".vsd" => "application/vnd.visio",
|
||||
".vsf" => "application/vnd.vsf",
|
||||
".vtt" => "text/vtt",
|
||||
".vtu" => "model/vnd.vtu",
|
||||
".vxml" => "application/voicexml+xml",
|
||||
".war" => "application/java-archive",
|
||||
".wasm" => "application/wasm",
|
||||
".wav" => "audio/x-wav",
|
||||
".wax" => "audio/x-ms-wax",
|
||||
".wbmp" => "image/vnd.wap.wbmp",
|
||||
".wbs" => "application/vnd.criticaltools.wbs+xml",
|
||||
".wbxml" => "application/vnd.wap.wbxml",
|
||||
".webm" => "video/webm",
|
||||
".wm" => "video/x-ms-wm",
|
||||
".wma" => "audio/x-ms-wma",
|
||||
".wmd" => "application/x-ms-wmd",
|
||||
".wmf" => "application/x-msmetafile",
|
||||
".wml" => "text/vnd.wap.wml",
|
||||
".wmlc" => "application/vnd.wap.wmlc",
|
||||
".wmls" => "text/vnd.wap.wmlscript",
|
||||
".wmlsc" => "application/vnd.wap.wmlscriptc",
|
||||
".wmv" => "video/x-ms-wmv",
|
||||
".wmx" => "video/x-ms-wmx",
|
||||
".wmz" => "application/x-ms-wmz",
|
||||
".woff" => "application/font-woff",
|
||||
".woff2" => "application/font-woff2",
|
||||
".wpd" => "application/vnd.wordperfect",
|
||||
".wpl" => "application/vnd.ms-wpl",
|
||||
".wps" => "application/vnd.ms-works",
|
||||
".wqd" => "application/vnd.wqd",
|
||||
".wri" => "application/x-mswrite",
|
||||
".wrl" => "model/vrml",
|
||||
".wsdl" => "application/wsdl+xml",
|
||||
".wspolicy" => "application/wspolicy+xml",
|
||||
".wtb" => "application/vnd.webturbo",
|
||||
".wvx" => "video/x-ms-wvx",
|
||||
".x3d" => "application/vnd.hzn-3d-crossword",
|
||||
".xar" => "application/vnd.xara",
|
||||
".xbd" => "application/vnd.fujixerox.docuworks.binder",
|
||||
".xbm" => "image/x-xbitmap",
|
||||
".xdm" => "application/vnd.syncml.dm+xml",
|
||||
".xdp" => "application/vnd.adobe.xdp+xml",
|
||||
".xdw" => "application/vnd.fujixerox.docuworks",
|
||||
".xenc" => "application/xenc+xml",
|
||||
".xer" => "application/patch-ops-error+xml",
|
||||
".xfdf" => "application/vnd.adobe.xfdf",
|
||||
".xfdl" => "application/vnd.xfdl",
|
||||
".xhtml" => "application/xhtml+xml",
|
||||
".xif" => "image/vnd.xiff",
|
||||
".xla" => "application/vnd.ms-excel",
|
||||
".xlam" => "application/vnd.ms-excel.addin.macroEnabled.12",
|
||||
".xls" => "application/vnd.ms-excel",
|
||||
".xlsb" => "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
".xlsm" => "application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||
".xlt" => "application/vnd.ms-excel",
|
||||
".xltx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
".xml" => "application/xml",
|
||||
".xo" => "application/vnd.olpc-sugar",
|
||||
".xop" => "application/xop+xml",
|
||||
".xpm" => "image/x-xpixmap",
|
||||
".xpr" => "application/vnd.is-xpr",
|
||||
".xps" => "application/vnd.ms-xpsdocument",
|
||||
".xpw" => "application/vnd.intercon.formnet",
|
||||
".xsl" => "application/xml",
|
||||
".xslt" => "application/xslt+xml",
|
||||
".xsm" => "application/vnd.syncml+xml",
|
||||
".xspf" => "application/xspf+xml",
|
||||
".xul" => "application/vnd.mozilla.xul+xml",
|
||||
".xwd" => "image/x-xwindowdump",
|
||||
".xyz" => "chemical/x-xyz",
|
||||
".yaml" => "text/yaml",
|
||||
".yml" => "text/yaml",
|
||||
".zaz" => "application/vnd.zzazz.deck+xml",
|
||||
".zip" => "application/zip",
|
||||
".zmm" => "application/vnd.handheld-entertainment+xml",
|
||||
}
|
||||
end
|
||||
end
|
||||
273
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/mock.rb
vendored
Normal file
273
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/mock.rb
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'uri'
|
||||
require 'stringio'
|
||||
require_relative '../rack'
|
||||
require 'cgi/cookie'
|
||||
|
||||
module Rack
|
||||
# Rack::MockRequest helps testing your Rack application without
|
||||
# actually using HTTP.
|
||||
#
|
||||
# After performing a request on a URL with get/post/put/patch/delete, it
|
||||
# returns a MockResponse with useful helper methods for effective
|
||||
# testing.
|
||||
#
|
||||
# You can pass a hash with additional configuration to the
|
||||
# get/post/put/patch/delete.
|
||||
# <tt>:input</tt>:: A String or IO-like to be used as rack.input.
|
||||
# <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
|
||||
# <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
|
||||
|
||||
class MockRequest
|
||||
class FatalWarning < RuntimeError
|
||||
end
|
||||
|
||||
class FatalWarner
|
||||
def puts(warning)
|
||||
raise FatalWarning, warning
|
||||
end
|
||||
|
||||
def write(warning)
|
||||
raise FatalWarning, warning
|
||||
end
|
||||
|
||||
def flush
|
||||
end
|
||||
|
||||
def string
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
DEFAULT_ENV = {
|
||||
RACK_VERSION => Rack::VERSION,
|
||||
RACK_INPUT => StringIO.new,
|
||||
RACK_ERRORS => StringIO.new,
|
||||
RACK_MULTITHREAD => true,
|
||||
RACK_MULTIPROCESS => true,
|
||||
RACK_RUNONCE => false,
|
||||
}.freeze
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
# Make a GET request and return a MockResponse. See #request.
|
||||
def get(uri, opts = {}) request(GET, uri, opts) end
|
||||
# Make a POST request and return a MockResponse. See #request.
|
||||
def post(uri, opts = {}) request(POST, uri, opts) end
|
||||
# Make a PUT request and return a MockResponse. See #request.
|
||||
def put(uri, opts = {}) request(PUT, uri, opts) end
|
||||
# Make a PATCH request and return a MockResponse. See #request.
|
||||
def patch(uri, opts = {}) request(PATCH, uri, opts) end
|
||||
# Make a DELETE request and return a MockResponse. See #request.
|
||||
def delete(uri, opts = {}) request(DELETE, uri, opts) end
|
||||
# Make a HEAD request and return a MockResponse. See #request.
|
||||
def head(uri, opts = {}) request(HEAD, uri, opts) end
|
||||
# Make an OPTIONS request and return a MockResponse. See #request.
|
||||
def options(uri, opts = {}) request(OPTIONS, uri, opts) end
|
||||
|
||||
# Make a request using the given request method for the given
|
||||
# uri to the rack application and return a MockResponse.
|
||||
# Options given are passed to MockRequest.env_for.
|
||||
def request(method = GET, uri = "", opts = {})
|
||||
env = self.class.env_for(uri, opts.merge(method: method))
|
||||
|
||||
if opts[:lint]
|
||||
app = Rack::Lint.new(@app)
|
||||
else
|
||||
app = @app
|
||||
end
|
||||
|
||||
errors = env[RACK_ERRORS]
|
||||
status, headers, body = app.call(env)
|
||||
MockResponse.new(status, headers, body, errors)
|
||||
ensure
|
||||
body.close if body.respond_to?(:close)
|
||||
end
|
||||
|
||||
# For historical reasons, we're pinning to RFC 2396.
|
||||
# URI::Parser = URI::RFC2396_Parser
|
||||
def self.parse_uri_rfc2396(uri)
|
||||
@parser ||= URI::Parser.new
|
||||
@parser.parse(uri)
|
||||
end
|
||||
|
||||
# Return the Rack environment used for a request to +uri+.
|
||||
# All options that are strings are added to the returned environment.
|
||||
# Options:
|
||||
# :fatal :: Whether to raise an exception if request outputs to rack.errors
|
||||
# :input :: The rack.input to set
|
||||
# :method :: The HTTP request method to use
|
||||
# :params :: The params to use
|
||||
# :script_name :: The SCRIPT_NAME to set
|
||||
def self.env_for(uri = "", opts = {})
|
||||
uri = parse_uri_rfc2396(uri)
|
||||
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
||||
|
||||
env = DEFAULT_ENV.dup
|
||||
|
||||
env[REQUEST_METHOD] = (opts[:method] ? opts[:method].to_s.upcase : GET).b
|
||||
env[SERVER_NAME] = (uri.host || "example.org").b
|
||||
env[SERVER_PORT] = (uri.port ? uri.port.to_s : "80").b
|
||||
env[QUERY_STRING] = (uri.query.to_s).b
|
||||
env[PATH_INFO] = ((!uri.path || uri.path.empty?) ? "/" : uri.path).b
|
||||
env[RACK_URL_SCHEME] = (uri.scheme || "http").b
|
||||
env[HTTPS] = (env[RACK_URL_SCHEME] == "https" ? "on" : "off").b
|
||||
|
||||
env[SCRIPT_NAME] = opts[:script_name] || ""
|
||||
|
||||
if opts[:fatal]
|
||||
env[RACK_ERRORS] = FatalWarner.new
|
||||
else
|
||||
env[RACK_ERRORS] = StringIO.new
|
||||
end
|
||||
|
||||
if params = opts[:params]
|
||||
if env[REQUEST_METHOD] == GET
|
||||
params = Utils.parse_nested_query(params) if params.is_a?(String)
|
||||
params.update(Utils.parse_nested_query(env[QUERY_STRING]))
|
||||
env[QUERY_STRING] = Utils.build_nested_query(params)
|
||||
elsif !opts.has_key?(:input)
|
||||
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
|
||||
if params.is_a?(Hash)
|
||||
if data = Rack::Multipart.build_multipart(params)
|
||||
opts[:input] = data
|
||||
opts["CONTENT_LENGTH"] ||= data.length.to_s
|
||||
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Rack::Multipart::MULTIPART_BOUNDARY}"
|
||||
else
|
||||
opts[:input] = Utils.build_nested_query(params)
|
||||
end
|
||||
else
|
||||
opts[:input] = params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
empty_str = String.new
|
||||
opts[:input] ||= empty_str
|
||||
if String === opts[:input]
|
||||
rack_input = StringIO.new(opts[:input])
|
||||
else
|
||||
rack_input = opts[:input]
|
||||
end
|
||||
|
||||
rack_input.set_encoding(Encoding::BINARY)
|
||||
env[RACK_INPUT] = rack_input
|
||||
|
||||
env["CONTENT_LENGTH"] ||= env[RACK_INPUT].size.to_s if env[RACK_INPUT].respond_to?(:size)
|
||||
|
||||
opts.each { |field, value|
|
||||
env[field] = value if String === field
|
||||
}
|
||||
|
||||
env
|
||||
end
|
||||
end
|
||||
|
||||
# Rack::MockResponse provides useful helpers for testing your apps.
|
||||
# Usually, you don't create the MockResponse on your own, but use
|
||||
# MockRequest.
|
||||
|
||||
class MockResponse < Rack::Response
|
||||
class << self
|
||||
alias [] new
|
||||
end
|
||||
|
||||
# Headers
|
||||
attr_reader :original_headers, :cookies
|
||||
|
||||
# Errors
|
||||
attr_accessor :errors
|
||||
|
||||
def initialize(status, headers, body, errors = StringIO.new(""))
|
||||
@original_headers = headers
|
||||
@errors = errors.string if errors.respond_to?(:string)
|
||||
@cookies = parse_cookies_from_header
|
||||
|
||||
super(body, status, headers)
|
||||
|
||||
buffered_body!
|
||||
end
|
||||
|
||||
def =~(other)
|
||||
body =~ other
|
||||
end
|
||||
|
||||
def match(other)
|
||||
body.match other
|
||||
end
|
||||
|
||||
def body
|
||||
# FIXME: apparently users of MockResponse expect the return value of
|
||||
# MockResponse#body to be a string. However, the real response object
|
||||
# returns the body as a list.
|
||||
#
|
||||
# See spec_showstatus.rb:
|
||||
#
|
||||
# should "not replace existing messages" do
|
||||
# ...
|
||||
# res.body.should == "foo!"
|
||||
# end
|
||||
buffer = String.new
|
||||
|
||||
super.each do |chunk|
|
||||
buffer << chunk
|
||||
end
|
||||
|
||||
return buffer
|
||||
end
|
||||
|
||||
def empty?
|
||||
[201, 204, 304].include? status
|
||||
end
|
||||
|
||||
def cookie(name)
|
||||
cookies.fetch(name, nil)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_cookies_from_header
|
||||
cookies = Hash.new
|
||||
if original_headers.has_key? 'Set-Cookie'
|
||||
set_cookie_header = original_headers.fetch('Set-Cookie')
|
||||
set_cookie_header.split("\n").each do |cookie|
|
||||
cookie_name, cookie_filling = cookie.split('=', 2)
|
||||
cookie_attributes = identify_cookie_attributes cookie_filling
|
||||
parsed_cookie = CGI::Cookie.new(
|
||||
'name' => cookie_name.strip,
|
||||
'value' => cookie_attributes.fetch('value'),
|
||||
'path' => cookie_attributes.fetch('path', nil),
|
||||
'domain' => cookie_attributes.fetch('domain', nil),
|
||||
'expires' => cookie_attributes.fetch('expires', nil),
|
||||
'secure' => cookie_attributes.fetch('secure', false)
|
||||
)
|
||||
cookies.store(cookie_name, parsed_cookie)
|
||||
end
|
||||
end
|
||||
cookies
|
||||
end
|
||||
|
||||
def identify_cookie_attributes(cookie_filling)
|
||||
cookie_bits = cookie_filling.split(';')
|
||||
cookie_attributes = Hash.new
|
||||
cookie_attributes.store('value', cookie_bits[0].strip)
|
||||
cookie_bits.each do |bit|
|
||||
if bit.include? '='
|
||||
cookie_attribute, attribute_value = bit.split('=')
|
||||
cookie_attributes.store(cookie_attribute.strip, attribute_value.strip)
|
||||
if cookie_attribute.include? 'max-age'
|
||||
cookie_attributes.store('expires', Time.now + attribute_value.strip.to_i)
|
||||
end
|
||||
end
|
||||
if bit.include? 'secure'
|
||||
cookie_attributes.store('secure', true)
|
||||
end
|
||||
end
|
||||
cookie_attributes
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
65
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart.rb
vendored
Normal file
65
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart.rb
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'multipart/parser'
|
||||
|
||||
module Rack
|
||||
# A multipart form data parser, adapted from IOWA.
|
||||
#
|
||||
# Usually, Rack::Request#POST takes care of calling this.
|
||||
module Multipart
|
||||
autoload :UploadedFile, 'rack/multipart/uploaded_file'
|
||||
autoload :Generator, 'rack/multipart/generator'
|
||||
|
||||
EOL = "\r\n"
|
||||
MULTIPART_BOUNDARY = "AaB03x"
|
||||
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
|
||||
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
|
||||
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
|
||||
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
|
||||
BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
|
||||
BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
|
||||
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
|
||||
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
|
||||
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
|
||||
# Updated definitions from RFC 2231
|
||||
ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}
|
||||
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
|
||||
SECTION = /\*[0-9]+/
|
||||
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
|
||||
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
|
||||
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
|
||||
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
|
||||
EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
|
||||
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
|
||||
EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
|
||||
EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
|
||||
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
|
||||
DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
|
||||
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
|
||||
|
||||
class << self
|
||||
def parse_multipart(env, params = Rack::Utils.default_query_parser)
|
||||
extract_multipart Rack::Request.new(env), params
|
||||
end
|
||||
|
||||
def extract_multipart(req, params = Rack::Utils.default_query_parser)
|
||||
io = req.get_header(RACK_INPUT)
|
||||
io.rewind
|
||||
content_length = req.content_length
|
||||
content_length = content_length.to_i if content_length
|
||||
|
||||
tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
|
||||
bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
|
||||
|
||||
info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
|
||||
req.set_header(RACK_TEMPFILES, info.tmp_files)
|
||||
info.params
|
||||
end
|
||||
|
||||
def build_multipart(params, first = true)
|
||||
Generator.new(params, first).dump
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
97
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart/generator.rb
vendored
Normal file
97
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart/generator.rb
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
module Multipart
|
||||
class Generator
|
||||
def initialize(params, first = true)
|
||||
@params, @first = params, first
|
||||
|
||||
if @first && !@params.is_a?(Hash)
|
||||
raise ArgumentError, "value must be a Hash"
|
||||
end
|
||||
end
|
||||
|
||||
def dump
|
||||
return nil if @first && !multipart?
|
||||
return flattened_params unless @first
|
||||
|
||||
flattened_params.map do |name, file|
|
||||
if file.respond_to?(:original_filename)
|
||||
if file.path
|
||||
::File.open(file.path, 'rb') do |f|
|
||||
f.set_encoding(Encoding::BINARY)
|
||||
content_for_tempfile(f, file, name)
|
||||
end
|
||||
else
|
||||
content_for_tempfile(file, file, name)
|
||||
end
|
||||
else
|
||||
content_for_other(file, name)
|
||||
end
|
||||
end.join << "--#{MULTIPART_BOUNDARY}--\r"
|
||||
end
|
||||
|
||||
private
|
||||
def multipart?
|
||||
query = lambda { |value|
|
||||
case value
|
||||
when Array
|
||||
value.any?(&query)
|
||||
when Hash
|
||||
value.values.any?(&query)
|
||||
when Rack::Multipart::UploadedFile
|
||||
true
|
||||
end
|
||||
}
|
||||
|
||||
@params.values.any?(&query)
|
||||
end
|
||||
|
||||
def flattened_params
|
||||
@flattened_params ||= begin
|
||||
h = Hash.new
|
||||
@params.each do |key, value|
|
||||
k = @first ? key.to_s : "[#{key}]"
|
||||
|
||||
case value
|
||||
when Array
|
||||
value.map { |v|
|
||||
Multipart.build_multipart(v, false).each { |subkey, subvalue|
|
||||
h["#{k}[]#{subkey}"] = subvalue
|
||||
}
|
||||
}
|
||||
when Hash
|
||||
Multipart.build_multipart(value, false).each { |subkey, subvalue|
|
||||
h[k + subkey] = subvalue
|
||||
}
|
||||
else
|
||||
h[k] = value
|
||||
end
|
||||
end
|
||||
h
|
||||
end
|
||||
end
|
||||
|
||||
def content_for_tempfile(io, file, name)
|
||||
length = ::File.stat(file.path).size if file.path
|
||||
filename = "; filename=\"#{Utils.escape(file.original_filename)}\"" if file.original_filename
|
||||
<<-EOF
|
||||
--#{MULTIPART_BOUNDARY}\r
|
||||
Content-Disposition: form-data; name="#{name}"#{filename}\r
|
||||
Content-Type: #{file.content_type}\r
|
||||
#{"Content-Length: #{length}\r\n" if length}\r
|
||||
#{io.read}\r
|
||||
EOF
|
||||
end
|
||||
|
||||
def content_for_other(file, name)
|
||||
<<-EOF
|
||||
--#{MULTIPART_BOUNDARY}\r
|
||||
Content-Disposition: form-data; name="#{name}"\r
|
||||
\r
|
||||
#{file}\r
|
||||
EOF
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
364
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart/parser.rb
vendored
Normal file
364
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart/parser.rb
vendored
Normal file
@ -0,0 +1,364 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'strscan'
|
||||
|
||||
module Rack
|
||||
module Multipart
|
||||
class MultipartPartLimitError < Errno::EMFILE; end
|
||||
|
||||
class Parser
|
||||
(require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
BUFSIZE = 1_048_576
|
||||
TEXT_PLAIN = "text/plain"
|
||||
TEMPFILE_FACTORY = lambda { |filename, content_type|
|
||||
Tempfile.new(["RackMultipart", ::File.extname(filename.gsub("\0", '%00'))])
|
||||
}
|
||||
|
||||
BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
|
||||
|
||||
class BoundedIO # :nodoc:
|
||||
def initialize(io, content_length)
|
||||
@io = io
|
||||
@content_length = content_length
|
||||
@cursor = 0
|
||||
end
|
||||
|
||||
def read(size, outbuf = nil)
|
||||
return if @cursor >= @content_length
|
||||
|
||||
left = @content_length - @cursor
|
||||
|
||||
str = if left < size
|
||||
@io.read left, outbuf
|
||||
else
|
||||
@io.read size, outbuf
|
||||
end
|
||||
|
||||
if str
|
||||
@cursor += str.bytesize
|
||||
else
|
||||
# Raise an error for mismatching Content-Length and actual contents
|
||||
raise EOFError, "bad content body"
|
||||
end
|
||||
|
||||
str
|
||||
end
|
||||
|
||||
def rewind
|
||||
@io.rewind
|
||||
end
|
||||
end
|
||||
|
||||
MultipartInfo = Struct.new :params, :tmp_files
|
||||
EMPTY = MultipartInfo.new(nil, [])
|
||||
|
||||
def self.parse_boundary(content_type)
|
||||
return unless content_type
|
||||
data = content_type.match(MULTIPART)
|
||||
return unless data
|
||||
data[1]
|
||||
end
|
||||
|
||||
def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
|
||||
return EMPTY if 0 == content_length
|
||||
|
||||
boundary = parse_boundary content_type
|
||||
return EMPTY unless boundary
|
||||
|
||||
io = BoundedIO.new(io, content_length) if content_length
|
||||
outbuf = String.new
|
||||
|
||||
parser = new(boundary, tmpfile, bufsize, qp)
|
||||
parser.on_read io.read(bufsize, outbuf)
|
||||
|
||||
loop do
|
||||
break if parser.state == :DONE
|
||||
parser.on_read io.read(bufsize, outbuf)
|
||||
end
|
||||
|
||||
io.rewind
|
||||
parser.result
|
||||
end
|
||||
|
||||
class Collector
|
||||
class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
|
||||
def get_data
|
||||
data = body
|
||||
if filename == ""
|
||||
# filename is blank which means no file has been selected
|
||||
return
|
||||
elsif filename
|
||||
body.rewind if body.respond_to?(:rewind)
|
||||
|
||||
# Take the basename of the upload's original filename.
|
||||
# This handles the full Windows paths given by Internet Explorer
|
||||
# (and perhaps other broken user agents) without affecting
|
||||
# those which give the lone filename.
|
||||
fn = filename.split(/[\/\\]/).last
|
||||
|
||||
data = { filename: fn, type: content_type,
|
||||
name: name, tempfile: body, head: head }
|
||||
end
|
||||
|
||||
yield data
|
||||
end
|
||||
end
|
||||
|
||||
class BufferPart < MimePart
|
||||
def file?; false; end
|
||||
def close; end
|
||||
end
|
||||
|
||||
class TempfilePart < MimePart
|
||||
def file?; true; end
|
||||
def close; body.close; end
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
|
||||
def initialize(tempfile)
|
||||
@tempfile = tempfile
|
||||
@mime_parts = []
|
||||
@open_files = 0
|
||||
end
|
||||
|
||||
def each
|
||||
@mime_parts.each { |part| yield part }
|
||||
end
|
||||
|
||||
def on_mime_head(mime_index, head, filename, content_type, name)
|
||||
if filename
|
||||
body = @tempfile.call(filename, content_type)
|
||||
body.binmode if body.respond_to?(:binmode)
|
||||
klass = TempfilePart
|
||||
@open_files += 1
|
||||
else
|
||||
body = String.new
|
||||
klass = BufferPart
|
||||
end
|
||||
|
||||
@mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
|
||||
|
||||
check_open_files
|
||||
end
|
||||
|
||||
def on_mime_body(mime_index, content)
|
||||
@mime_parts[mime_index].body << content
|
||||
end
|
||||
|
||||
def on_mime_finish(mime_index)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_open_files
|
||||
if Utils.multipart_part_limit > 0
|
||||
if @open_files >= Utils.multipart_part_limit
|
||||
@mime_parts.each(&:close)
|
||||
raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :state
|
||||
|
||||
def initialize(boundary, tempfile, bufsize, query_parser)
|
||||
@query_parser = query_parser
|
||||
@params = query_parser.make_params
|
||||
@boundary = "--#{boundary}"
|
||||
@bufsize = bufsize
|
||||
|
||||
@full_boundary = @boundary
|
||||
@end_boundary = @boundary + '--'
|
||||
@state = :FAST_FORWARD
|
||||
@mime_index = 0
|
||||
@collector = Collector.new tempfile
|
||||
|
||||
@sbuf = StringScanner.new("".dup)
|
||||
@body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
|
||||
@rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
|
||||
@head_regex = /(.*?#{EOL})#{EOL}/m
|
||||
end
|
||||
|
||||
def on_read(content)
|
||||
handle_empty_content!(content)
|
||||
@sbuf.concat content
|
||||
run_parser
|
||||
end
|
||||
|
||||
def result
|
||||
@collector.each do |part|
|
||||
part.get_data do |data|
|
||||
tag_multipart_encoding(part.filename, part.content_type, part.name, data)
|
||||
@query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
|
||||
end
|
||||
end
|
||||
MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run_parser
|
||||
loop do
|
||||
case @state
|
||||
when :FAST_FORWARD
|
||||
break if handle_fast_forward == :want_read
|
||||
when :CONSUME_TOKEN
|
||||
break if handle_consume_token == :want_read
|
||||
when :MIME_HEAD
|
||||
break if handle_mime_head == :want_read
|
||||
when :MIME_BODY
|
||||
break if handle_mime_body == :want_read
|
||||
when :DONE
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_fast_forward
|
||||
if consume_boundary
|
||||
@state = :MIME_HEAD
|
||||
else
|
||||
raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
|
||||
:want_read
|
||||
end
|
||||
end
|
||||
|
||||
def handle_consume_token
|
||||
tok = consume_boundary
|
||||
# break if we're at the end of a buffer, but not if it is the end of a field
|
||||
@state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
|
||||
:DONE
|
||||
else
|
||||
:MIME_HEAD
|
||||
end
|
||||
end
|
||||
|
||||
def handle_mime_head
|
||||
if @sbuf.scan_until(@head_regex)
|
||||
head = @sbuf[1]
|
||||
content_type = head[MULTIPART_CONTENT_TYPE, 1]
|
||||
if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
|
||||
name = Rack::Auth::Digest::Params::dequote(name)
|
||||
else
|
||||
name = head[MULTIPART_CONTENT_ID, 1]
|
||||
end
|
||||
|
||||
filename = get_filename(head)
|
||||
|
||||
if name.nil? || name.empty?
|
||||
name = filename || "#{content_type || TEXT_PLAIN}[]".dup
|
||||
end
|
||||
|
||||
@collector.on_mime_head @mime_index, head, filename, content_type, name
|
||||
@state = :MIME_BODY
|
||||
else
|
||||
:want_read
|
||||
end
|
||||
end
|
||||
|
||||
def handle_mime_body
|
||||
if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
|
||||
body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
|
||||
@collector.on_mime_body @mime_index, body
|
||||
@sbuf.pos += body.length + 2 # skip \r\n after the content
|
||||
@state = :CONSUME_TOKEN
|
||||
@mime_index += 1
|
||||
else
|
||||
# Save what we have so far
|
||||
if @rx_max_size < @sbuf.rest_size
|
||||
delta = @sbuf.rest_size - @rx_max_size
|
||||
@collector.on_mime_body @mime_index, @sbuf.peek(delta)
|
||||
@sbuf.pos += delta
|
||||
@sbuf.string = @sbuf.rest
|
||||
end
|
||||
:want_read
|
||||
end
|
||||
end
|
||||
|
||||
def full_boundary; @full_boundary; end
|
||||
|
||||
def consume_boundary
|
||||
while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
|
||||
case read_buffer.strip
|
||||
when full_boundary then return :BOUNDARY
|
||||
when @end_boundary then return :END_BOUNDARY
|
||||
end
|
||||
return if @sbuf.eos?
|
||||
end
|
||||
end
|
||||
|
||||
def get_filename(head)
|
||||
filename = nil
|
||||
case head
|
||||
when RFC2183
|
||||
params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
|
||||
|
||||
if filename = params['filename']
|
||||
filename = $1 if filename =~ /^"(.*)"$/
|
||||
elsif filename = params['filename*']
|
||||
encoding, _, filename = filename.split("'", 3)
|
||||
end
|
||||
when BROKEN_QUOTED, BROKEN_UNQUOTED
|
||||
filename = $1
|
||||
end
|
||||
|
||||
return unless filename
|
||||
|
||||
if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
|
||||
filename = Utils.unescape_path(filename)
|
||||
end
|
||||
|
||||
filename.scrub!
|
||||
|
||||
if filename !~ /\\[^\\"]/
|
||||
filename = filename.gsub(/\\(.)/, '\1')
|
||||
end
|
||||
|
||||
if encoding
|
||||
filename.force_encoding ::Encoding.find(encoding)
|
||||
end
|
||||
|
||||
filename
|
||||
end
|
||||
|
||||
CHARSET = "charset"
|
||||
|
||||
def tag_multipart_encoding(filename, content_type, name, body)
|
||||
name = name.to_s
|
||||
encoding = Encoding::UTF_8
|
||||
|
||||
name.force_encoding(encoding)
|
||||
|
||||
return if filename
|
||||
|
||||
if content_type
|
||||
list = content_type.split(';')
|
||||
type_subtype = list.first
|
||||
type_subtype.strip!
|
||||
if TEXT_PLAIN == type_subtype
|
||||
rest = list.drop 1
|
||||
rest.each do |param|
|
||||
k, v = param.split('=', 2)
|
||||
k.strip!
|
||||
v.strip!
|
||||
v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
|
||||
encoding = Encoding.find v if k == CHARSET
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
name.force_encoding(encoding)
|
||||
body.force_encoding(encoding)
|
||||
end
|
||||
|
||||
def handle_empty_content!(content)
|
||||
if content.nil? || content.empty?
|
||||
raise EOFError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart/uploaded_file.rb
vendored
Normal file
41
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/multipart/uploaded_file.rb
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
module Multipart
|
||||
class UploadedFile
|
||||
# The filename, *not* including the path, of the "uploaded" file
|
||||
attr_reader :original_filename
|
||||
|
||||
# The content type of the "uploaded" file
|
||||
attr_accessor :content_type
|
||||
|
||||
def initialize(filepath = nil, ct = "text/plain", bin = false,
|
||||
path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
|
||||
if io
|
||||
@tempfile = io
|
||||
@original_filename = filename
|
||||
else
|
||||
raise "#{path} file does not exist" unless ::File.exist?(path)
|
||||
@original_filename = filename || ::File.basename(path)
|
||||
@tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
|
||||
@tempfile.binmode if binary
|
||||
FileUtils.copy_file(path, @tempfile.path)
|
||||
end
|
||||
@content_type = content_type
|
||||
end
|
||||
|
||||
def path
|
||||
@tempfile.path if @tempfile.respond_to?(:path)
|
||||
end
|
||||
alias_method :local_path, :path
|
||||
|
||||
def respond_to?(*args)
|
||||
super or @tempfile.respond_to?(*args)
|
||||
end
|
||||
|
||||
def method_missing(method_name, *args, &block) #:nodoc:
|
||||
@tempfile.__send__(method_name, *args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
39
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/null_logger.rb
vendored
Normal file
39
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/null_logger.rb
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
class NullLogger
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
env[RACK_LOGGER] = self
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
def info(progname = nil, &block); end
|
||||
def debug(progname = nil, &block); end
|
||||
def warn(progname = nil, &block); end
|
||||
def error(progname = nil, &block); end
|
||||
def fatal(progname = nil, &block); end
|
||||
def unknown(progname = nil, &block); end
|
||||
def info? ; end
|
||||
def debug? ; end
|
||||
def warn? ; end
|
||||
def error? ; end
|
||||
def fatal? ; end
|
||||
def level ; end
|
||||
def progname ; end
|
||||
def datetime_format ; end
|
||||
def formatter ; end
|
||||
def sev_threshold ; end
|
||||
def level=(level); end
|
||||
def progname=(progname); end
|
||||
def datetime_format=(datetime_format); end
|
||||
def formatter=(formatter); end
|
||||
def sev_threshold=(sev_threshold); end
|
||||
def close ; end
|
||||
def add(severity, message = nil, progname = nil, &block); end
|
||||
def <<(msg); end
|
||||
end
|
||||
end
|
||||
217
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/query_parser.rb
vendored
Normal file
217
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/query_parser.rb
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
class QueryParser
|
||||
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
DEFAULT_SEP = /[&;] */n
|
||||
COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
|
||||
|
||||
# ParameterTypeError is the error that is raised when incoming structural
|
||||
# parameters (parsed by parse_nested_query) contain conflicting types.
|
||||
class ParameterTypeError < TypeError; end
|
||||
|
||||
# InvalidParameterError is the error that is raised when incoming structural
|
||||
# parameters (parsed by parse_nested_query) contain invalid format or byte
|
||||
# sequence.
|
||||
class InvalidParameterError < ArgumentError; end
|
||||
|
||||
def self.make_default(key_space_limit, param_depth_limit)
|
||||
new Params, key_space_limit, param_depth_limit
|
||||
end
|
||||
|
||||
attr_reader :key_space_limit, :param_depth_limit
|
||||
|
||||
def initialize(params_class, key_space_limit, param_depth_limit)
|
||||
@params_class = params_class
|
||||
@key_space_limit = key_space_limit
|
||||
@param_depth_limit = param_depth_limit
|
||||
end
|
||||
|
||||
# Stolen from Mongrel, with some small modifications:
|
||||
# Parses a query string by breaking it up at the '&'
|
||||
# and ';' characters. You can also use this to parse
|
||||
# cookies by changing the characters used in the second
|
||||
# parameter (which defaults to '&;').
|
||||
def parse_query(qs, d = nil, &unescaper)
|
||||
unescaper ||= method(:unescape)
|
||||
|
||||
params = make_params
|
||||
|
||||
(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
||||
next if p.empty?
|
||||
k, v = p.split('=', 2).map!(&unescaper)
|
||||
|
||||
if cur = params[k]
|
||||
if cur.class == Array
|
||||
params[k] << v
|
||||
else
|
||||
params[k] = [cur, v]
|
||||
end
|
||||
else
|
||||
params[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return params.to_h
|
||||
end
|
||||
|
||||
# parse_nested_query expands a query string into structural types. Supported
|
||||
# types are Arrays, Hashes and basic value types. It is possible to supply
|
||||
# query strings with parameters of conflicting types, in this case a
|
||||
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
||||
# case.
|
||||
def parse_nested_query(qs, d = nil)
|
||||
params = make_params
|
||||
|
||||
unless qs.nil? || qs.empty?
|
||||
(qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
|
||||
k, v = p.split('=', 2).map! { |s| unescape(s) }
|
||||
|
||||
normalize_params(params, k, v, param_depth_limit)
|
||||
end
|
||||
end
|
||||
|
||||
return params.to_h
|
||||
rescue ArgumentError => e
|
||||
raise InvalidParameterError, e.message, e.backtrace
|
||||
end
|
||||
|
||||
# normalize_params recursively expands parameters into structural types. If
|
||||
# the structural types represented by two different parameter names are in
|
||||
# conflict, a ParameterTypeError is raised.
|
||||
def normalize_params(params, name, v, depth)
|
||||
raise RangeError if depth <= 0
|
||||
|
||||
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
||||
k = $1 || ''
|
||||
after = $' || ''
|
||||
|
||||
if k.empty?
|
||||
if !v.nil? && name == "[]"
|
||||
return Array(v)
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if after == ''
|
||||
params[k] = v
|
||||
elsif after == "["
|
||||
params[name] = v
|
||||
elsif after == "[]"
|
||||
params[k] ||= []
|
||||
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
||||
params[k] << v
|
||||
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
||||
child_key = $1
|
||||
params[k] ||= []
|
||||
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
||||
if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
|
||||
normalize_params(params[k].last, child_key, v, depth - 1)
|
||||
else
|
||||
params[k] << normalize_params(make_params, child_key, v, depth - 1)
|
||||
end
|
||||
else
|
||||
params[k] ||= make_params
|
||||
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
||||
params[k] = normalize_params(params[k], after, v, depth - 1)
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def make_params
|
||||
@params_class.new @key_space_limit
|
||||
end
|
||||
|
||||
def new_space_limit(key_space_limit)
|
||||
self.class.new @params_class, key_space_limit, param_depth_limit
|
||||
end
|
||||
|
||||
def new_depth_limit(param_depth_limit)
|
||||
self.class.new @params_class, key_space_limit, param_depth_limit
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def params_hash_type?(obj)
|
||||
obj.kind_of?(@params_class)
|
||||
end
|
||||
|
||||
def params_hash_has_key?(hash, key)
|
||||
return false if /\[\]/.match?(key)
|
||||
|
||||
key.split(/[\[\]]+/).inject(hash) do |h, part|
|
||||
next h if part == ''
|
||||
return false unless params_hash_type?(h) && h.key?(part)
|
||||
h[part]
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def unescape(s)
|
||||
Utils.unescape(s)
|
||||
end
|
||||
|
||||
class Params
|
||||
def initialize(limit)
|
||||
@limit = limit
|
||||
@size = 0
|
||||
@params = {}
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@params[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@size += key.size if key && !@params.key?(key)
|
||||
raise RangeError, 'exceeded available parameter key space' if @size > @limit
|
||||
@params[key] = value
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@params.key?(key)
|
||||
end
|
||||
|
||||
# Recursively unwraps nested `Params` objects and constructs an object
|
||||
# of the same shape, but using the objects' internal representations
|
||||
# (Ruby hashes) in place of the objects. The result is a hash consisting
|
||||
# purely of Ruby primitives.
|
||||
#
|
||||
# Mutation warning!
|
||||
#
|
||||
# 1. This method mutates the internal representation of the `Params`
|
||||
# objects in order to save object allocations.
|
||||
#
|
||||
# 2. The value you get back is a reference to the internal hash
|
||||
# representation, not a copy.
|
||||
#
|
||||
# 3. Because the `Params` object's internal representation is mutable
|
||||
# through the `#[]=` method, it is not thread safe. The result of
|
||||
# getting the hash representation while another thread is adding a
|
||||
# key to it is non-deterministic.
|
||||
#
|
||||
def to_h
|
||||
@params.each do |key, value|
|
||||
case value
|
||||
when self
|
||||
# Handle circular references gracefully.
|
||||
@params[key] = @params
|
||||
when Params
|
||||
@params[key] = value.to_h
|
||||
when Array
|
||||
value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
|
||||
else
|
||||
# Ignore anything that is not a `Params` object or
|
||||
# a collection that can contain one.
|
||||
end
|
||||
end
|
||||
@params
|
||||
end
|
||||
alias_method :to_params_hash, :to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
64
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/recursive.rb
vendored
Normal file
64
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/recursive.rb
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'uri'
|
||||
|
||||
module Rack
|
||||
# Rack::ForwardRequest gets caught by Rack::Recursive and redirects
|
||||
# the current request to the app at +url+.
|
||||
#
|
||||
# raise ForwardRequest.new("/not-found")
|
||||
#
|
||||
|
||||
class ForwardRequest < Exception
|
||||
attr_reader :url, :env
|
||||
|
||||
def initialize(url, env = {})
|
||||
@url = URI(url)
|
||||
@env = env
|
||||
|
||||
@env[PATH_INFO] = @url.path
|
||||
@env[QUERY_STRING] = @url.query if @url.query
|
||||
@env[HTTP_HOST] = @url.host if @url.host
|
||||
@env[HTTP_PORT] = @url.port if @url.port
|
||||
@env[RACK_URL_SCHEME] = @url.scheme if @url.scheme
|
||||
|
||||
super "forwarding to #{url}"
|
||||
end
|
||||
end
|
||||
|
||||
# Rack::Recursive allows applications called down the chain to
|
||||
# include data from other applications (by using
|
||||
# <tt>rack['rack.recursive.include'][...]</tt> or raise a
|
||||
# ForwardRequest to redirect internally.
|
||||
|
||||
class Recursive
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
dup._call(env)
|
||||
end
|
||||
|
||||
def _call(env)
|
||||
@script_name = env[SCRIPT_NAME]
|
||||
@app.call(env.merge(RACK_RECURSIVE_INCLUDE => method(:include)))
|
||||
rescue ForwardRequest => req
|
||||
call(env.merge(req.env))
|
||||
end
|
||||
|
||||
def include(env, path)
|
||||
unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ ||
|
||||
path[@script_name.size].nil?)
|
||||
raise ArgumentError, "can only include below #{@script_name}, not #{path}"
|
||||
end
|
||||
|
||||
env = env.merge(PATH_INFO => path,
|
||||
SCRIPT_NAME => @script_name,
|
||||
REQUEST_METHOD => GET,
|
||||
"CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "",
|
||||
RACK_INPUT => StringIO.new(""))
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
114
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/reloader.rb
vendored
Normal file
114
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/reloader.rb
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Copyright (C) 2009-2018 Michael Fellinger <m.fellinger@gmail.com>
|
||||
# Rack::Reloader is subject to the terms of an MIT-style license.
|
||||
# See MIT-LICENSE or https://opensource.org/licenses/MIT.
|
||||
|
||||
require 'pathname'
|
||||
|
||||
module Rack
|
||||
|
||||
# High performant source reloader
|
||||
#
|
||||
# This class acts as Rack middleware.
|
||||
#
|
||||
# What makes it especially suited for use in a production environment is that
|
||||
# any file will only be checked once and there will only be made one system
|
||||
# call stat(2).
|
||||
#
|
||||
# Please note that this will not reload files in the background, it does so
|
||||
# only when actively called.
|
||||
#
|
||||
# It is performing a check/reload cycle at the start of every request, but
|
||||
# also respects a cool down time, during which nothing will be done.
|
||||
class Reloader
|
||||
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
def initialize(app, cooldown = 10, backend = Stat)
|
||||
@app = app
|
||||
@cooldown = cooldown
|
||||
@last = (Time.now - cooldown)
|
||||
@cache = {}
|
||||
@mtimes = {}
|
||||
@reload_mutex = Mutex.new
|
||||
|
||||
extend backend
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if @cooldown and Time.now > @last + @cooldown
|
||||
if Thread.list.size > 1
|
||||
@reload_mutex.synchronize{ reload! }
|
||||
else
|
||||
reload!
|
||||
end
|
||||
|
||||
@last = Time.now
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
def reload!(stderr = $stderr)
|
||||
rotation do |file, mtime|
|
||||
previous_mtime = @mtimes[file] ||= mtime
|
||||
safe_load(file, mtime, stderr) if mtime > previous_mtime
|
||||
end
|
||||
end
|
||||
|
||||
# A safe Kernel::load, issuing the hooks depending on the results
|
||||
def safe_load(file, mtime, stderr = $stderr)
|
||||
load(file)
|
||||
stderr.puts "#{self.class}: reloaded `#{file}'"
|
||||
file
|
||||
rescue LoadError, SyntaxError => ex
|
||||
stderr.puts ex
|
||||
ensure
|
||||
@mtimes[file] = mtime
|
||||
end
|
||||
|
||||
module Stat
|
||||
def rotation
|
||||
files = [$0, *$LOADED_FEATURES].uniq
|
||||
paths = ['./', *$LOAD_PATH].uniq
|
||||
|
||||
files.map{|file|
|
||||
next if /\.(so|bundle)$/.match?(file) # cannot reload compiled files
|
||||
|
||||
found, stat = figure_path(file, paths)
|
||||
next unless found && stat && mtime = stat.mtime
|
||||
|
||||
@cache[file] = found
|
||||
|
||||
yield(found, mtime)
|
||||
}.compact
|
||||
end
|
||||
|
||||
# Takes a relative or absolute +file+ name, a couple possible +paths+ that
|
||||
# the +file+ might reside in. Returns the full path and File::Stat for the
|
||||
# path.
|
||||
def figure_path(file, paths)
|
||||
found = @cache[file]
|
||||
found = file if !found and Pathname.new(file).absolute?
|
||||
found, stat = safe_stat(found)
|
||||
return found, stat if found
|
||||
|
||||
paths.find do |possible_path|
|
||||
path = ::File.join(possible_path, file)
|
||||
found, stat = safe_stat(path)
|
||||
return ::File.expand_path(found), stat if found
|
||||
end
|
||||
|
||||
return false, false
|
||||
end
|
||||
|
||||
def safe_stat(file)
|
||||
return unless file
|
||||
stat = ::File.stat(file)
|
||||
return file, stat if stat.file?
|
||||
rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ESRCH
|
||||
@cache.delete(file) and false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
659
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/request.rb
vendored
Normal file
659
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/request.rb
vendored
Normal file
@ -0,0 +1,659 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Rack::Request provides a convenient interface to a Rack
|
||||
# environment. It is stateless, the environment +env+ passed to the
|
||||
# constructor will be directly modified.
|
||||
#
|
||||
# req = Rack::Request.new(env)
|
||||
# req.post?
|
||||
# req.params["data"]
|
||||
|
||||
class Request
|
||||
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
class << self
|
||||
attr_accessor :ip_filter
|
||||
end
|
||||
|
||||
self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
|
||||
ALLOWED_SCHEMES = %w(https http).freeze
|
||||
SCHEME_WHITELIST = ALLOWED_SCHEMES
|
||||
if Object.respond_to?(:deprecate_constant)
|
||||
deprecate_constant :SCHEME_WHITELIST
|
||||
end
|
||||
|
||||
def initialize(env)
|
||||
@params = nil
|
||||
super(env)
|
||||
end
|
||||
|
||||
def params
|
||||
@params ||= super
|
||||
end
|
||||
|
||||
def update_param(k, v)
|
||||
super
|
||||
@params = nil
|
||||
end
|
||||
|
||||
def delete_param(k)
|
||||
v = super
|
||||
@params = nil
|
||||
v
|
||||
end
|
||||
|
||||
module Env
|
||||
# The environment of the request.
|
||||
attr_reader :env
|
||||
|
||||
def initialize(env)
|
||||
@env = env
|
||||
super()
|
||||
end
|
||||
|
||||
# Predicate method to test to see if `name` has been set as request
|
||||
# specific data
|
||||
def has_header?(name)
|
||||
@env.key? name
|
||||
end
|
||||
|
||||
# Get a request specific value for `name`.
|
||||
def get_header(name)
|
||||
@env[name]
|
||||
end
|
||||
|
||||
# If a block is given, it yields to the block if the value hasn't been set
|
||||
# on the request.
|
||||
def fetch_header(name, &block)
|
||||
@env.fetch(name, &block)
|
||||
end
|
||||
|
||||
# Loops through each key / value pair in the request specific data.
|
||||
def each_header(&block)
|
||||
@env.each(&block)
|
||||
end
|
||||
|
||||
# Set a request specific value for `name` to `v`
|
||||
def set_header(name, v)
|
||||
@env[name] = v
|
||||
end
|
||||
|
||||
# Add a header that may have multiple values.
|
||||
#
|
||||
# Example:
|
||||
# request.add_header 'Accept', 'image/png'
|
||||
# request.add_header 'Accept', '*/*'
|
||||
#
|
||||
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
||||
#
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
def add_header(key, v)
|
||||
if v.nil?
|
||||
get_header key
|
||||
elsif has_header? key
|
||||
set_header key, "#{get_header key},#{v}"
|
||||
else
|
||||
set_header key, v
|
||||
end
|
||||
end
|
||||
|
||||
# Delete a request specific value for `name`.
|
||||
def delete_header(name)
|
||||
@env.delete name
|
||||
end
|
||||
|
||||
def initialize_copy(other)
|
||||
@env = other.env.dup
|
||||
end
|
||||
end
|
||||
|
||||
module Helpers
|
||||
# The set of form-data media-types. Requests that do not indicate
|
||||
# one of the media types present in this list will not be eligible
|
||||
# for form-data / param parsing.
|
||||
FORM_DATA_MEDIA_TYPES = [
|
||||
'application/x-www-form-urlencoded',
|
||||
'multipart/form-data'
|
||||
]
|
||||
|
||||
# The set of media-types. Requests that do not indicate
|
||||
# one of the media types present in this list will not be eligible
|
||||
# for param parsing like soap attachments or generic multiparts
|
||||
PARSEABLE_DATA_MEDIA_TYPES = [
|
||||
'multipart/related',
|
||||
'multipart/mixed'
|
||||
]
|
||||
|
||||
# Default ports depending on scheme. Used to decide whether or not
|
||||
# to include the port in a generated URI.
|
||||
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
||||
|
||||
# The address of the client which connected to the proxy.
|
||||
HTTP_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR'
|
||||
|
||||
# The contents of the host/:authority header sent to the proxy.
|
||||
HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
|
||||
|
||||
# The value of the scheme sent to the proxy.
|
||||
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
||||
|
||||
# The protocol used to connect to the proxy.
|
||||
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
||||
|
||||
# The port used to connect to the proxy.
|
||||
HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
|
||||
|
||||
# Another way for specifing https scheme was used.
|
||||
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
|
||||
|
||||
def body; get_header(RACK_INPUT) end
|
||||
def script_name; get_header(SCRIPT_NAME).to_s end
|
||||
def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
|
||||
|
||||
def path_info; get_header(PATH_INFO).to_s end
|
||||
def path_info=(s); set_header(PATH_INFO, s.to_s) end
|
||||
|
||||
def request_method; get_header(REQUEST_METHOD) end
|
||||
def query_string; get_header(QUERY_STRING).to_s end
|
||||
def content_length; get_header('CONTENT_LENGTH') end
|
||||
def logger; get_header(RACK_LOGGER) end
|
||||
def user_agent; get_header('HTTP_USER_AGENT') end
|
||||
def multithread?; get_header(RACK_MULTITHREAD) end
|
||||
|
||||
# the referer of the client
|
||||
def referer; get_header('HTTP_REFERER') end
|
||||
alias referrer referer
|
||||
|
||||
def session
|
||||
fetch_header(RACK_SESSION) do |k|
|
||||
set_header RACK_SESSION, default_session
|
||||
end
|
||||
end
|
||||
|
||||
def session_options
|
||||
fetch_header(RACK_SESSION_OPTIONS) do |k|
|
||||
set_header RACK_SESSION_OPTIONS, {}
|
||||
end
|
||||
end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type DELETE
|
||||
def delete?; request_method == DELETE end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type GET
|
||||
def get?; request_method == GET end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type HEAD
|
||||
def head?; request_method == HEAD end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
|
||||
def options?; request_method == OPTIONS end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type LINK
|
||||
def link?; request_method == LINK end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type PATCH
|
||||
def patch?; request_method == PATCH end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type POST
|
||||
def post?; request_method == POST end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type PUT
|
||||
def put?; request_method == PUT end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type TRACE
|
||||
def trace?; request_method == TRACE end
|
||||
|
||||
# Checks the HTTP request method (or verb) to see if it was of type UNLINK
|
||||
def unlink?; request_method == UNLINK end
|
||||
|
||||
def scheme
|
||||
if get_header(HTTPS) == 'on'
|
||||
'https'
|
||||
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
|
||||
'https'
|
||||
elsif forwarded_scheme
|
||||
forwarded_scheme
|
||||
else
|
||||
get_header(RACK_URL_SCHEME)
|
||||
end
|
||||
end
|
||||
|
||||
# The authority of the incoming request as defined by RFC3976.
|
||||
# https://tools.ietf.org/html/rfc3986#section-3.2
|
||||
#
|
||||
# In HTTP/1, this is the `host` header.
|
||||
# In HTTP/2, this is the `:authority` pseudo-header.
|
||||
def authority
|
||||
forwarded_authority || host_authority || server_authority
|
||||
end
|
||||
|
||||
# The authority as defined by the `SERVER_NAME` and `SERVER_PORT`
|
||||
# variables.
|
||||
def server_authority
|
||||
host = self.server_name
|
||||
port = self.server_port
|
||||
|
||||
if host
|
||||
if port
|
||||
"#{host}:#{port}"
|
||||
else
|
||||
host
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def server_name
|
||||
get_header(SERVER_NAME)
|
||||
end
|
||||
|
||||
def server_port
|
||||
if port = get_header(SERVER_PORT)
|
||||
Integer(port)
|
||||
end
|
||||
end
|
||||
|
||||
def cookies
|
||||
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |key|
|
||||
set_header(key, {})
|
||||
end
|
||||
|
||||
string = get_header(HTTP_COOKIE)
|
||||
|
||||
unless string == get_header(RACK_REQUEST_COOKIE_STRING)
|
||||
hash.replace Utils.parse_cookies_header(string)
|
||||
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
def content_type
|
||||
content_type = get_header('CONTENT_TYPE')
|
||||
content_type.nil? || content_type.empty? ? nil : content_type
|
||||
end
|
||||
|
||||
def xhr?
|
||||
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
||||
end
|
||||
|
||||
# The `HTTP_HOST` header.
|
||||
def host_authority
|
||||
get_header(HTTP_HOST)
|
||||
end
|
||||
|
||||
def host_with_port(authority = self.authority)
|
||||
host, _, port = split_authority(authority)
|
||||
|
||||
if port == DEFAULT_PORTS[self.scheme]
|
||||
host
|
||||
else
|
||||
authority
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a formatted host, suitable for being used in a URI.
|
||||
def host
|
||||
split_authority(self.authority)[0]
|
||||
end
|
||||
|
||||
# Returns an address suitable for being to resolve to an address.
|
||||
# In the case of a domain name or IPv4 address, the result is the same
|
||||
# as +host+. In the case of IPv6 or future address formats, the square
|
||||
# brackets are removed.
|
||||
def hostname
|
||||
split_authority(self.authority)[1]
|
||||
end
|
||||
|
||||
def port
|
||||
if authority = self.authority
|
||||
_, _, port = split_authority(self.authority)
|
||||
|
||||
if port
|
||||
return port
|
||||
end
|
||||
end
|
||||
|
||||
if forwarded_port = self.forwarded_port
|
||||
return forwarded_port.first
|
||||
end
|
||||
|
||||
if scheme = self.scheme
|
||||
if port = DEFAULT_PORTS[self.scheme]
|
||||
return port
|
||||
end
|
||||
end
|
||||
|
||||
self.server_port
|
||||
end
|
||||
|
||||
def forwarded_for
|
||||
if value = get_header(HTTP_X_FORWARDED_FOR)
|
||||
split_header(value).map do |authority|
|
||||
split_authority(wrap_ipv6(authority))[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def forwarded_port
|
||||
if value = get_header(HTTP_X_FORWARDED_PORT)
|
||||
split_header(value).map(&:to_i)
|
||||
end
|
||||
end
|
||||
|
||||
def forwarded_authority
|
||||
if value = get_header(HTTP_X_FORWARDED_HOST)
|
||||
wrap_ipv6(split_header(value).first)
|
||||
end
|
||||
end
|
||||
|
||||
def ssl?
|
||||
scheme == 'https' || scheme == 'wss'
|
||||
end
|
||||
|
||||
def ip
|
||||
remote_addresses = split_header(get_header('REMOTE_ADDR'))
|
||||
external_addresses = reject_trusted_ip_addresses(remote_addresses)
|
||||
|
||||
unless external_addresses.empty?
|
||||
return external_addresses.first
|
||||
end
|
||||
|
||||
if forwarded_for = self.forwarded_for
|
||||
unless forwarded_for.empty?
|
||||
# The forwarded for addresses are ordered: client, proxy1, proxy2.
|
||||
# So we reject all the trusted addresses (proxy*) and return the
|
||||
# last client. Or if we trust everyone, we just return the first
|
||||
# address.
|
||||
return reject_trusted_ip_addresses(forwarded_for).last || forwarded_for.first
|
||||
end
|
||||
end
|
||||
|
||||
# If all the addresses are trusted, and we aren't forwarded, just return
|
||||
# the first remote address, which represents the source of the request.
|
||||
remote_addresses.first
|
||||
end
|
||||
|
||||
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
||||
# without any media type parameters. e.g., when CONTENT_TYPE is
|
||||
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
||||
#
|
||||
# For more information on the use of media types in HTTP, see:
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
||||
def media_type
|
||||
MediaType.type(content_type)
|
||||
end
|
||||
|
||||
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
||||
# an empty Hash if no CONTENT_TYPE or media-type parameters were
|
||||
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
|
||||
# this method responds with the following Hash:
|
||||
# { 'charset' => 'utf-8' }
|
||||
def media_type_params
|
||||
MediaType.params(content_type)
|
||||
end
|
||||
|
||||
# The character set of the request body if a "charset" media type
|
||||
# parameter was given, or nil if no "charset" was specified. Note
|
||||
# that, per RFC2616, text/* media types that specify no explicit
|
||||
# charset are to be considered ISO-8859-1.
|
||||
def content_charset
|
||||
media_type_params['charset']
|
||||
end
|
||||
|
||||
# Determine whether the request body contains form-data by checking
|
||||
# the request Content-Type for one of the media-types:
|
||||
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
||||
# list of form-data media types can be modified through the
|
||||
# +FORM_DATA_MEDIA_TYPES+ array.
|
||||
#
|
||||
# A request body is also assumed to contain form-data when no
|
||||
# Content-Type header is provided and the request_method is POST.
|
||||
def form_data?
|
||||
type = media_type
|
||||
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
||||
|
||||
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
||||
end
|
||||
|
||||
# Determine whether the request body contains data by checking
|
||||
# the request media_type against registered parse-data media-types
|
||||
def parseable_data?
|
||||
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
|
||||
end
|
||||
|
||||
# Returns the data received in the query string.
|
||||
def GET
|
||||
if get_header(RACK_REQUEST_QUERY_STRING) == query_string
|
||||
get_header(RACK_REQUEST_QUERY_HASH)
|
||||
else
|
||||
query_hash = parse_query(query_string, '&;')
|
||||
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
||||
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the data received in the request body.
|
||||
#
|
||||
# This method support both application/x-www-form-urlencoded and
|
||||
# multipart/form-data.
|
||||
def POST
|
||||
if get_header(RACK_INPUT).nil?
|
||||
raise "Missing rack.input"
|
||||
elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
|
||||
get_header(RACK_REQUEST_FORM_HASH)
|
||||
elsif form_data? || parseable_data?
|
||||
unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
|
||||
form_vars = get_header(RACK_INPUT).read
|
||||
|
||||
# Fix for Safari Ajax postings that always append \0
|
||||
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
||||
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
||||
|
||||
set_header RACK_REQUEST_FORM_VARS, form_vars
|
||||
set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
|
||||
|
||||
get_header(RACK_INPUT).rewind
|
||||
end
|
||||
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
||||
get_header RACK_REQUEST_FORM_HASH
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
# The union of GET and POST data.
|
||||
#
|
||||
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
||||
def params
|
||||
self.GET.merge(self.POST)
|
||||
end
|
||||
|
||||
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
||||
#
|
||||
# The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
||||
#
|
||||
# <tt>env['rack.input']</tt> is not touched.
|
||||
def update_param(k, v)
|
||||
found = false
|
||||
if self.GET.has_key?(k)
|
||||
found = true
|
||||
self.GET[k] = v
|
||||
end
|
||||
if self.POST.has_key?(k)
|
||||
found = true
|
||||
self.POST[k] = v
|
||||
end
|
||||
unless found
|
||||
self.GET[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
# Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
|
||||
#
|
||||
# If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
|
||||
#
|
||||
# <tt>env['rack.input']</tt> is not touched.
|
||||
def delete_param(k)
|
||||
post_value, get_value = self.POST.delete(k), self.GET.delete(k)
|
||||
post_value || get_value
|
||||
end
|
||||
|
||||
def base_url
|
||||
"#{scheme}://#{host_with_port}"
|
||||
end
|
||||
|
||||
# Tries to return a remake of the original request URL as a string.
|
||||
def url
|
||||
base_url + fullpath
|
||||
end
|
||||
|
||||
def path
|
||||
script_name + path_info
|
||||
end
|
||||
|
||||
def fullpath
|
||||
query_string.empty? ? path : "#{path}?#{query_string}"
|
||||
end
|
||||
|
||||
def accept_encoding
|
||||
parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
|
||||
end
|
||||
|
||||
def accept_language
|
||||
parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
|
||||
end
|
||||
|
||||
def trusted_proxy?(ip)
|
||||
Rack::Request.ip_filter.call(ip)
|
||||
end
|
||||
|
||||
# shortcut for <tt>request.params[key]</tt>
|
||||
def [](key)
|
||||
if $VERBOSE
|
||||
warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
|
||||
end
|
||||
|
||||
params[key.to_s]
|
||||
end
|
||||
|
||||
# shortcut for <tt>request.params[key] = value</tt>
|
||||
#
|
||||
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
||||
def []=(key, value)
|
||||
if $VERBOSE
|
||||
warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
|
||||
end
|
||||
|
||||
params[key.to_s] = value
|
||||
end
|
||||
|
||||
# like Hash#values_at
|
||||
def values_at(*keys)
|
||||
keys.map { |key| params[key] }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_session; {}; end
|
||||
|
||||
# Assist with compatibility when processing `X-Forwarded-For`.
|
||||
def wrap_ipv6(host)
|
||||
# Even thought IPv6 addresses should be wrapped in square brackets,
|
||||
# sometimes this is not done in various legacy/underspecified headers.
|
||||
# So we try to fix this situation for compatibility reasons.
|
||||
|
||||
# Try to detect IPv6 addresses which aren't escaped yet:
|
||||
if !host.start_with?('[') && host.count(':') > 1
|
||||
"[#{host}]"
|
||||
else
|
||||
host
|
||||
end
|
||||
end
|
||||
|
||||
def parse_http_accept_header(header)
|
||||
header.to_s.split(/\s*,\s*/).map do |part|
|
||||
attribute, parameters = part.split(/\s*;\s*/, 2)
|
||||
quality = 1.0
|
||||
if parameters and /\Aq=([\d.]+)/ =~ parameters
|
||||
quality = $1.to_f
|
||||
end
|
||||
[attribute, quality]
|
||||
end
|
||||
end
|
||||
|
||||
def query_parser
|
||||
Utils.default_query_parser
|
||||
end
|
||||
|
||||
def parse_query(qs, d = '&')
|
||||
query_parser.parse_nested_query(qs, d)
|
||||
end
|
||||
|
||||
def parse_multipart
|
||||
Rack::Multipart.extract_multipart(self, query_parser)
|
||||
end
|
||||
|
||||
def split_header(value)
|
||||
value ? value.strip.split(/[,\s]+/) : []
|
||||
end
|
||||
|
||||
AUTHORITY = /^
|
||||
# The host:
|
||||
(?<host>
|
||||
# An IPv6 address:
|
||||
(\[(?<ip6>.*)\])
|
||||
|
|
||||
# An IPv4 address:
|
||||
(?<ip4>[\d\.]+)
|
||||
|
|
||||
# A hostname:
|
||||
(?<name>[a-zA-Z0-9\.\-]+)
|
||||
)
|
||||
# The optional port:
|
||||
(:(?<port>\d+))?
|
||||
$/x
|
||||
|
||||
private_constant :AUTHORITY
|
||||
|
||||
def split_authority(authority)
|
||||
if match = AUTHORITY.match(authority)
|
||||
if address = match[:ip6]
|
||||
return match[:host], address, match[:port]&.to_i
|
||||
else
|
||||
return match[:host], match[:host], match[:port]&.to_i
|
||||
end
|
||||
end
|
||||
|
||||
# Give up!
|
||||
return authority, authority, nil
|
||||
end
|
||||
|
||||
def reject_trusted_ip_addresses(ip_addresses)
|
||||
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
||||
end
|
||||
|
||||
def forwarded_scheme
|
||||
allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
|
||||
allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
|
||||
end
|
||||
|
||||
def allowed_scheme(header)
|
||||
header if ALLOWED_SCHEMES.include?(header)
|
||||
end
|
||||
|
||||
def extract_proto_header(header)
|
||||
if header
|
||||
if (comma_index = header.index(','))
|
||||
header[0, comma_index]
|
||||
else
|
||||
header
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include Env
|
||||
include Helpers
|
||||
end
|
||||
end
|
||||
318
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/response.rb
vendored
Normal file
318
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/response.rb
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'time'
|
||||
|
||||
module Rack
|
||||
# Rack::Response provides a convenient interface to create a Rack
|
||||
# response.
|
||||
#
|
||||
# It allows setting of headers and cookies, and provides useful
|
||||
# defaults (an OK response with empty headers and body).
|
||||
#
|
||||
# You can use Response#write to iteratively generate your response,
|
||||
# but note that this is buffered by Rack::Response until you call
|
||||
# +finish+. +finish+ however can take a block inside which calls to
|
||||
# +write+ are synchronous with the Rack response.
|
||||
#
|
||||
# Your application's +call+ should end returning Response#finish.
|
||||
class Response
|
||||
def self.[](status, headers, body)
|
||||
self.new(body, status, headers)
|
||||
end
|
||||
|
||||
CHUNKED = 'chunked'
|
||||
STATUS_WITH_NO_ENTITY_BODY = Utils::STATUS_WITH_NO_ENTITY_BODY
|
||||
|
||||
attr_accessor :length, :status, :body
|
||||
attr_reader :headers
|
||||
|
||||
# @deprecated Use {#headers} instead.
|
||||
alias header headers
|
||||
|
||||
# Initialize the response object with the specified body, status
|
||||
# and headers.
|
||||
#
|
||||
# @param body [nil, #each, #to_str] the response body.
|
||||
# @param status [Integer] the integer status as defined by the
|
||||
# HTTP protocol RFCs.
|
||||
# @param headers [#each] a list of key-value header pairs which
|
||||
# conform to the HTTP protocol RFCs.
|
||||
#
|
||||
# Providing a body which responds to #to_str is legacy behaviour.
|
||||
def initialize(body = nil, status = 200, headers = {})
|
||||
@status = status.to_i
|
||||
@headers = Utils::HeaderHash[headers]
|
||||
|
||||
@writer = self.method(:append)
|
||||
|
||||
@block = nil
|
||||
|
||||
# Keep track of whether we have expanded the user supplied body.
|
||||
if body.nil?
|
||||
@body = []
|
||||
@buffered = true
|
||||
@length = 0
|
||||
elsif body.respond_to?(:to_str)
|
||||
@body = [body]
|
||||
@buffered = true
|
||||
@length = body.to_str.bytesize
|
||||
else
|
||||
@body = body
|
||||
@buffered = false
|
||||
@length = 0
|
||||
end
|
||||
|
||||
yield self if block_given?
|
||||
end
|
||||
|
||||
def redirect(target, status = 302)
|
||||
self.status = status
|
||||
self.location = target
|
||||
end
|
||||
|
||||
def chunked?
|
||||
CHUNKED == get_header(TRANSFER_ENCODING)
|
||||
end
|
||||
|
||||
# Generate a response array consistent with the requirements of the SPEC.
|
||||
# @return [Array] a 3-tuple suitable of `[status, headers, body]`
|
||||
# which is suitable to be returned from the middleware `#call(env)` method.
|
||||
def finish(&block)
|
||||
if STATUS_WITH_NO_ENTITY_BODY[status.to_i]
|
||||
delete_header CONTENT_TYPE
|
||||
delete_header CONTENT_LENGTH
|
||||
close
|
||||
return [@status, @headers, []]
|
||||
else
|
||||
if block_given?
|
||||
@block = block
|
||||
return [@status, @headers, self]
|
||||
else
|
||||
return [@status, @headers, @body]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias to_a finish # For *response
|
||||
|
||||
def each(&callback)
|
||||
@body.each(&callback)
|
||||
@buffered = true
|
||||
|
||||
if @block
|
||||
@writer = callback
|
||||
@block.call(self)
|
||||
end
|
||||
end
|
||||
|
||||
# Append to body and update Content-Length.
|
||||
#
|
||||
# NOTE: Do not mix #write and direct #body access!
|
||||
#
|
||||
def write(chunk)
|
||||
buffered_body!
|
||||
|
||||
@writer.call(chunk.to_s)
|
||||
end
|
||||
|
||||
def close
|
||||
@body.close if @body.respond_to?(:close)
|
||||
end
|
||||
|
||||
def empty?
|
||||
@block == nil && @body.empty?
|
||||
end
|
||||
|
||||
def has_header?(key); headers.key? key; end
|
||||
def get_header(key); headers[key]; end
|
||||
def set_header(key, v); headers[key] = v; end
|
||||
def delete_header(key); headers.delete key; end
|
||||
|
||||
alias :[] :get_header
|
||||
alias :[]= :set_header
|
||||
|
||||
module Helpers
|
||||
def invalid?; status < 100 || status >= 600; end
|
||||
|
||||
def informational?; status >= 100 && status < 200; end
|
||||
def successful?; status >= 200 && status < 300; end
|
||||
def redirection?; status >= 300 && status < 400; end
|
||||
def client_error?; status >= 400 && status < 500; end
|
||||
def server_error?; status >= 500 && status < 600; end
|
||||
|
||||
def ok?; status == 200; end
|
||||
def created?; status == 201; end
|
||||
def accepted?; status == 202; end
|
||||
def no_content?; status == 204; end
|
||||
def moved_permanently?; status == 301; end
|
||||
def bad_request?; status == 400; end
|
||||
def unauthorized?; status == 401; end
|
||||
def forbidden?; status == 403; end
|
||||
def not_found?; status == 404; end
|
||||
def method_not_allowed?; status == 405; end
|
||||
def precondition_failed?; status == 412; end
|
||||
def unprocessable?; status == 422; end
|
||||
|
||||
def redirect?; [301, 302, 303, 307, 308].include? status; end
|
||||
|
||||
def include?(header)
|
||||
has_header? header
|
||||
end
|
||||
|
||||
# Add a header that may have multiple values.
|
||||
#
|
||||
# Example:
|
||||
# response.add_header 'Vary', 'Accept-Encoding'
|
||||
# response.add_header 'Vary', 'Cookie'
|
||||
#
|
||||
# assert_equal 'Accept-Encoding,Cookie', response.get_header('Vary')
|
||||
#
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
def add_header(key, v)
|
||||
if v.nil?
|
||||
get_header key
|
||||
elsif has_header? key
|
||||
set_header key, "#{get_header key},#{v}"
|
||||
else
|
||||
set_header key, v
|
||||
end
|
||||
end
|
||||
|
||||
# Get the content type of the response.
|
||||
def content_type
|
||||
get_header CONTENT_TYPE
|
||||
end
|
||||
|
||||
# Set the content type of the response.
|
||||
def content_type=(content_type)
|
||||
set_header CONTENT_TYPE, content_type
|
||||
end
|
||||
|
||||
def media_type
|
||||
MediaType.type(content_type)
|
||||
end
|
||||
|
||||
def media_type_params
|
||||
MediaType.params(content_type)
|
||||
end
|
||||
|
||||
def content_length
|
||||
cl = get_header CONTENT_LENGTH
|
||||
cl ? cl.to_i : cl
|
||||
end
|
||||
|
||||
def location
|
||||
get_header "Location"
|
||||
end
|
||||
|
||||
def location=(location)
|
||||
set_header "Location", location
|
||||
end
|
||||
|
||||
def set_cookie(key, value)
|
||||
cookie_header = get_header SET_COOKIE
|
||||
set_header SET_COOKIE, ::Rack::Utils.add_cookie_to_header(cookie_header, key, value)
|
||||
end
|
||||
|
||||
def delete_cookie(key, value = {})
|
||||
set_header SET_COOKIE, ::Rack::Utils.add_remove_cookie_to_header(get_header(SET_COOKIE), key, value)
|
||||
end
|
||||
|
||||
def set_cookie_header
|
||||
get_header SET_COOKIE
|
||||
end
|
||||
|
||||
def set_cookie_header=(v)
|
||||
set_header SET_COOKIE, v
|
||||
end
|
||||
|
||||
def cache_control
|
||||
get_header CACHE_CONTROL
|
||||
end
|
||||
|
||||
def cache_control=(v)
|
||||
set_header CACHE_CONTROL, v
|
||||
end
|
||||
|
||||
# Specifies that the content shouldn't be cached. Overrides `cache!` if already called.
|
||||
def do_not_cache!
|
||||
set_header CACHE_CONTROL, "no-cache, must-revalidate"
|
||||
set_header EXPIRES, Time.now.httpdate
|
||||
end
|
||||
|
||||
# Specify that the content should be cached.
|
||||
# @param duration [Integer] The number of seconds until the cache expires.
|
||||
# @option directive [String] The cache control directive, one of "public", "private", "no-cache" or "no-store".
|
||||
def cache!(duration = 3600, directive: "public")
|
||||
unless headers[CACHE_CONTROL] =~ /no-cache/
|
||||
set_header CACHE_CONTROL, "#{directive}, max-age=#{duration}"
|
||||
set_header EXPIRES, (Time.now + duration).httpdate
|
||||
end
|
||||
end
|
||||
|
||||
def etag
|
||||
get_header ETAG
|
||||
end
|
||||
|
||||
def etag=(v)
|
||||
set_header ETAG, v
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def buffered_body!
|
||||
return if @buffered
|
||||
|
||||
if @body.is_a?(Array)
|
||||
# The user supplied body was an array:
|
||||
@body = @body.compact
|
||||
@body.each do |part|
|
||||
@length += part.to_s.bytesize
|
||||
end
|
||||
else
|
||||
# Turn the user supplied body into a buffered array:
|
||||
body = @body
|
||||
@body = Array.new
|
||||
|
||||
body.each do |part|
|
||||
@writer.call(part.to_s)
|
||||
end
|
||||
|
||||
body.close if body.respond_to?(:close)
|
||||
end
|
||||
|
||||
@buffered = true
|
||||
end
|
||||
|
||||
def append(chunk)
|
||||
@body << chunk
|
||||
|
||||
unless chunked?
|
||||
@length += chunk.bytesize
|
||||
set_header(CONTENT_LENGTH, @length.to_s)
|
||||
end
|
||||
|
||||
return chunk
|
||||
end
|
||||
end
|
||||
|
||||
include Helpers
|
||||
|
||||
class Raw
|
||||
include Helpers
|
||||
|
||||
attr_reader :headers
|
||||
attr_accessor :status
|
||||
|
||||
def initialize(status, headers)
|
||||
@status = status
|
||||
@headers = headers
|
||||
end
|
||||
|
||||
def has_header?(key); headers.key? key; end
|
||||
def get_header(key); headers[key]; end
|
||||
def set_header(key, v); headers[key] = v; end
|
||||
def delete_header(key); headers.delete key; end
|
||||
end
|
||||
end
|
||||
end
|
||||
94
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/rewindable_input.rb
vendored
Normal file
94
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/rewindable_input.rb
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
# -*- encoding: binary -*-
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'tempfile'
|
||||
|
||||
module Rack
|
||||
# Class which can make any IO object rewindable, including non-rewindable ones. It does
|
||||
# this by buffering the data into a tempfile, which is rewindable.
|
||||
#
|
||||
# rack.input is required to be rewindable, so if your input stream IO is non-rewindable
|
||||
# by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
|
||||
# to easily make it rewindable.
|
||||
#
|
||||
# Don't forget to call #close when you're done. This frees up temporary resources that
|
||||
# RewindableInput uses, though it does *not* close the original IO object.
|
||||
class RewindableInput
|
||||
def initialize(io)
|
||||
@io = io
|
||||
@rewindable_io = nil
|
||||
@unlinked = false
|
||||
end
|
||||
|
||||
def gets
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.gets
|
||||
end
|
||||
|
||||
def read(*args)
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.read(*args)
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.each(&block)
|
||||
end
|
||||
|
||||
def rewind
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.rewind
|
||||
end
|
||||
|
||||
# Closes this RewindableInput object without closing the originally
|
||||
# wrapped IO object. Cleans up any temporary resources that this RewindableInput
|
||||
# has created.
|
||||
#
|
||||
# This method may be called multiple times. It does nothing on subsequent calls.
|
||||
def close
|
||||
if @rewindable_io
|
||||
if @unlinked
|
||||
@rewindable_io.close
|
||||
else
|
||||
@rewindable_io.close!
|
||||
end
|
||||
@rewindable_io = nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def make_rewindable
|
||||
# Buffer all data into a tempfile. Since this tempfile is private to this
|
||||
# RewindableInput object, we chmod it so that nobody else can read or write
|
||||
# it. On POSIX filesystems we also unlink the file so that it doesn't
|
||||
# even have a file entry on the filesystem anymore, though we can still
|
||||
# access it because we have the file handle open.
|
||||
@rewindable_io = Tempfile.new('RackRewindableInput')
|
||||
@rewindable_io.chmod(0000)
|
||||
@rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
|
||||
@rewindable_io.binmode
|
||||
if filesystem_has_posix_semantics?
|
||||
raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
|
||||
@unlinked = true
|
||||
end
|
||||
|
||||
buffer = "".dup
|
||||
while @io.read(1024 * 4, buffer)
|
||||
entire_buffer_written_out = false
|
||||
while !entire_buffer_written_out
|
||||
written = @rewindable_io.write(buffer)
|
||||
entire_buffer_written_out = written == buffer.bytesize
|
||||
if !entire_buffer_written_out
|
||||
buffer.slice!(0 .. written - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
@rewindable_io.rewind
|
||||
end
|
||||
|
||||
def filesystem_has_posix_semantics?
|
||||
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
|
||||
end
|
||||
end
|
||||
end
|
||||
34
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/runtime.rb
vendored
Normal file
34
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/runtime.rb
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
# Sets an "X-Runtime" response header, indicating the response
|
||||
# time of the request, in seconds
|
||||
#
|
||||
# You can put it right before the application to see the processing
|
||||
# time, or before all the other middlewares to include time for them,
|
||||
# too.
|
||||
class Runtime
|
||||
FORMAT_STRING = "%0.6f" # :nodoc:
|
||||
HEADER_NAME = "X-Runtime" # :nodoc:
|
||||
|
||||
def initialize(app, name = nil)
|
||||
@app = app
|
||||
@header_name = HEADER_NAME
|
||||
@header_name += "-#{name}" if name
|
||||
end
|
||||
|
||||
def call(env)
|
||||
start_time = Utils.clock_time
|
||||
status, headers, body = @app.call(env)
|
||||
headers = Utils::HeaderHash[headers]
|
||||
|
||||
request_time = Utils.clock_time - start_time
|
||||
|
||||
unless headers.key?(@header_name)
|
||||
headers[@header_name] = FORMAT_STRING % request_time
|
||||
end
|
||||
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
end
|
||||
162
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/sendfile.rb
vendored
Normal file
162
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/sendfile.rb
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
|
||||
# = Sendfile
|
||||
#
|
||||
# The Sendfile middleware intercepts responses whose body is being
|
||||
# served from a file and replaces it with a server specific X-Sendfile
|
||||
# header. The web server is then responsible for writing the file contents
|
||||
# to the client. This can dramatically reduce the amount of work required
|
||||
# by the Ruby backend and takes advantage of the web server's optimized file
|
||||
# delivery code.
|
||||
#
|
||||
# In order to take advantage of this middleware, the response body must
|
||||
# respond to +to_path+ and the request must include an X-Sendfile-Type
|
||||
# header. Rack::Files and other components implement +to_path+ so there's
|
||||
# rarely anything you need to do in your application. The X-Sendfile-Type
|
||||
# header is typically set in your web servers configuration. The following
|
||||
# sections attempt to document
|
||||
#
|
||||
# === Nginx
|
||||
#
|
||||
# Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile
|
||||
# but requires parts of the filesystem to be mapped into a private URL
|
||||
# hierarchy.
|
||||
#
|
||||
# The following example shows the Nginx configuration required to create
|
||||
# a private "/files/" area, enable X-Accel-Redirect, and pass the special
|
||||
# X-Sendfile-Type and X-Accel-Mapping headers to the backend:
|
||||
#
|
||||
# location ~ /files/(.*) {
|
||||
# internal;
|
||||
# alias /var/www/$1;
|
||||
# }
|
||||
#
|
||||
# location / {
|
||||
# proxy_redirect off;
|
||||
#
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
#
|
||||
# proxy_set_header X-Sendfile-Type X-Accel-Redirect;
|
||||
# proxy_set_header X-Accel-Mapping /var/www/=/files/;
|
||||
#
|
||||
# proxy_pass http://127.0.0.1:8080/;
|
||||
# }
|
||||
#
|
||||
# Note that the X-Sendfile-Type header must be set exactly as shown above.
|
||||
# The X-Accel-Mapping header should specify the location on the file system,
|
||||
# followed by an equals sign (=), followed name of the private URL pattern
|
||||
# that it maps to. The middleware performs a simple substitution on the
|
||||
# resulting path.
|
||||
#
|
||||
# See Also: https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile
|
||||
#
|
||||
# === lighttpd
|
||||
#
|
||||
# Lighttpd has supported some variation of the X-Sendfile header for some
|
||||
# time, although only recent version support X-Sendfile in a reverse proxy
|
||||
# configuration.
|
||||
#
|
||||
# $HTTP["host"] == "example.com" {
|
||||
# proxy-core.protocol = "http"
|
||||
# proxy-core.balancer = "round-robin"
|
||||
# proxy-core.backends = (
|
||||
# "127.0.0.1:8000",
|
||||
# "127.0.0.1:8001",
|
||||
# ...
|
||||
# )
|
||||
#
|
||||
# proxy-core.allow-x-sendfile = "enable"
|
||||
# proxy-core.rewrite-request = (
|
||||
# "X-Sendfile-Type" => (".*" => "X-Sendfile")
|
||||
# )
|
||||
# }
|
||||
#
|
||||
# See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore
|
||||
#
|
||||
# === Apache
|
||||
#
|
||||
# X-Sendfile is supported under Apache 2.x using a separate module:
|
||||
#
|
||||
# https://tn123.org/mod_xsendfile/
|
||||
#
|
||||
# Once the module is compiled and installed, you can enable it using
|
||||
# XSendFile config directive:
|
||||
#
|
||||
# RequestHeader Set X-Sendfile-Type X-Sendfile
|
||||
# ProxyPassReverse / http://localhost:8001/
|
||||
# XSendFile on
|
||||
#
|
||||
# === Mapping parameter
|
||||
#
|
||||
# The third parameter allows for an overriding extension of the
|
||||
# X-Accel-Mapping header. Mappings should be provided in tuples of internal to
|
||||
# external. The internal values may contain regular expression syntax, they
|
||||
# will be matched with case indifference.
|
||||
|
||||
class Sendfile
|
||||
def initialize(app, variation = nil, mappings = [])
|
||||
@app = app
|
||||
@variation = variation
|
||||
@mappings = mappings.map do |internal, external|
|
||||
[/^#{internal}/i, external]
|
||||
end
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
if body.respond_to?(:to_path)
|
||||
case type = variation(env)
|
||||
when 'X-Accel-Redirect'
|
||||
path = ::File.expand_path(body.to_path)
|
||||
if url = map_accel_path(env, path)
|
||||
headers[CONTENT_LENGTH] = '0'
|
||||
# '?' must be percent-encoded because it is not query string but a part of path
|
||||
headers[type] = ::Rack::Utils.escape_path(url).gsub('?', '%3F')
|
||||
obody = body
|
||||
body = Rack::BodyProxy.new([]) do
|
||||
obody.close if obody.respond_to?(:close)
|
||||
end
|
||||
else
|
||||
env[RACK_ERRORS].puts "X-Accel-Mapping header missing"
|
||||
end
|
||||
when 'X-Sendfile', 'X-Lighttpd-Send-File'
|
||||
path = ::File.expand_path(body.to_path)
|
||||
headers[CONTENT_LENGTH] = '0'
|
||||
headers[type] = path
|
||||
obody = body
|
||||
body = Rack::BodyProxy.new([]) do
|
||||
obody.close if obody.respond_to?(:close)
|
||||
end
|
||||
when '', nil
|
||||
else
|
||||
env[RACK_ERRORS].puts "Unknown x-sendfile variation: '#{type}'.\n"
|
||||
end
|
||||
end
|
||||
[status, headers, body]
|
||||
end
|
||||
|
||||
private
|
||||
def variation(env)
|
||||
@variation ||
|
||||
env['sendfile.type'] ||
|
||||
env['HTTP_X_SENDFILE_TYPE']
|
||||
end
|
||||
|
||||
def map_accel_path(env, path)
|
||||
if mapping = @mappings.find { |internal, _| internal =~ path }
|
||||
path.sub(*mapping)
|
||||
elsif mapping = env['HTTP_X_ACCEL_MAPPING']
|
||||
mapping.split(',').map(&:strip).each do |m|
|
||||
internal, external = m.split('=', 2).map(&:strip)
|
||||
new_path = path.sub(/^#{internal}/i, external)
|
||||
return new_path unless path == new_path
|
||||
end
|
||||
path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
466
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/server.rb
vendored
Normal file
466
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/server.rb
vendored
Normal file
@ -0,0 +1,466 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'optparse'
|
||||
require 'fileutils'
|
||||
|
||||
module Rack
|
||||
|
||||
class Server
|
||||
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
class Options
|
||||
def parse!(args)
|
||||
options = {}
|
||||
opt_parser = OptionParser.new("", 24, ' ') do |opts|
|
||||
opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
|
||||
|
||||
opts.separator ""
|
||||
opts.separator "Ruby options:"
|
||||
|
||||
lineno = 1
|
||||
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
||||
eval line, TOPLEVEL_BINDING, "-e", lineno
|
||||
lineno += 1
|
||||
}
|
||||
|
||||
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
||||
options[:debug] = true
|
||||
}
|
||||
opts.on("-w", "--warn", "turn warnings on for your script") {
|
||||
options[:warn] = true
|
||||
}
|
||||
opts.on("-q", "--quiet", "turn off logging") {
|
||||
options[:quiet] = true
|
||||
}
|
||||
|
||||
opts.on("-I", "--include PATH",
|
||||
"specify $LOAD_PATH (may be used more than once)") { |path|
|
||||
(options[:include] ||= []).concat(path.split(":"))
|
||||
}
|
||||
|
||||
opts.on("-r", "--require LIBRARY",
|
||||
"require the library, before executing your script") { |library|
|
||||
(options[:require] ||= []) << library
|
||||
}
|
||||
|
||||
opts.separator ""
|
||||
opts.separator "Rack options:"
|
||||
opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
|
||||
options[:builder] = line
|
||||
}
|
||||
|
||||
opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick)") { |s|
|
||||
options[:server] = s
|
||||
}
|
||||
|
||||
opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
|
||||
options[:Host] = host
|
||||
}
|
||||
|
||||
opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
|
||||
options[:Port] = port
|
||||
}
|
||||
|
||||
opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name|
|
||||
name, value = name.split('=', 2)
|
||||
value = true if value.nil?
|
||||
options[name.to_sym] = value
|
||||
}
|
||||
|
||||
opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
|
||||
options[:environment] = e
|
||||
}
|
||||
|
||||
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
||||
options[:daemonize] = d ? true : false
|
||||
}
|
||||
|
||||
opts.on("-P", "--pid FILE", "file to store PID") { |f|
|
||||
options[:pid] = ::File.expand_path(f)
|
||||
}
|
||||
|
||||
opts.separator ""
|
||||
opts.separator "Profiling options:"
|
||||
|
||||
opts.on("--heap HEAPFILE", "Build the application, then dump the heap to HEAPFILE") do |e|
|
||||
options[:heapfile] = e
|
||||
end
|
||||
|
||||
opts.on("--profile PROFILE", "Dump CPU or Memory profile to PROFILE (defaults to a tempfile)") do |e|
|
||||
options[:profile_file] = e
|
||||
end
|
||||
|
||||
opts.on("--profile-mode MODE", "Profile mode (cpu|wall|object)") do |e|
|
||||
{ cpu: true, wall: true, object: true }.fetch(e.to_sym) do
|
||||
raise OptionParser::InvalidOption, "unknown profile mode: #{e}"
|
||||
end
|
||||
options[:profile_mode] = e.to_sym
|
||||
end
|
||||
|
||||
opts.separator ""
|
||||
opts.separator "Common options:"
|
||||
|
||||
opts.on_tail("-h", "-?", "--help", "Show this message") do
|
||||
puts opts
|
||||
puts handler_opts(options)
|
||||
|
||||
exit
|
||||
end
|
||||
|
||||
opts.on_tail("--version", "Show version") do
|
||||
puts "Rack #{Rack.version} (Release: #{Rack.release})"
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
opt_parser.parse! args
|
||||
rescue OptionParser::InvalidOption => e
|
||||
warn e.message
|
||||
abort opt_parser.to_s
|
||||
end
|
||||
|
||||
options[:config] = args.last if args.last && !args.last.empty?
|
||||
options
|
||||
end
|
||||
|
||||
def handler_opts(options)
|
||||
begin
|
||||
info = []
|
||||
server = Rack::Handler.get(options[:server]) || Rack::Handler.default
|
||||
if server && server.respond_to?(:valid_options)
|
||||
info << ""
|
||||
info << "Server-specific options for #{server.name}:"
|
||||
|
||||
has_options = false
|
||||
server.valid_options.each do |name, description|
|
||||
next if /^(Host|Port)[^a-zA-Z]/.match?(name.to_s) # ignore handler's host and port options, we do our own.
|
||||
info << " -O %-21s %s" % [name, description]
|
||||
has_options = true
|
||||
end
|
||||
return "" if !has_options
|
||||
end
|
||||
info.join("\n")
|
||||
rescue NameError, LoadError
|
||||
return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Start a new rack server (like running rackup). This will parse ARGV and
|
||||
# provide standard ARGV rackup options, defaulting to load 'config.ru'.
|
||||
#
|
||||
# Providing an options hash will prevent ARGV parsing and will not include
|
||||
# any default options.
|
||||
#
|
||||
# This method can be used to very easily launch a CGI application, for
|
||||
# example:
|
||||
#
|
||||
# Rack::Server.start(
|
||||
# :app => lambda do |e|
|
||||
# [200, {'Content-Type' => 'text/html'}, ['hello world']]
|
||||
# end,
|
||||
# :server => 'cgi'
|
||||
# )
|
||||
#
|
||||
# Further options available here are documented on Rack::Server#initialize
|
||||
def self.start(options = nil)
|
||||
new(options).start
|
||||
end
|
||||
|
||||
attr_writer :options
|
||||
|
||||
# Options may include:
|
||||
# * :app
|
||||
# a rack application to run (overrides :config and :builder)
|
||||
# * :builder
|
||||
# a string to evaluate a Rack::Builder from
|
||||
# * :config
|
||||
# a rackup configuration file path to load (.ru)
|
||||
# * :environment
|
||||
# this selects the middleware that will be wrapped around
|
||||
# your application. Default options available are:
|
||||
# - development: CommonLogger, ShowExceptions, and Lint
|
||||
# - deployment: CommonLogger
|
||||
# - none: no extra middleware
|
||||
# note: when the server is a cgi server, CommonLogger is not included.
|
||||
# * :server
|
||||
# choose a specific Rack::Handler, e.g. cgi, fcgi, webrick
|
||||
# * :daemonize
|
||||
# if true, the server will daemonize itself (fork, detach, etc)
|
||||
# * :pid
|
||||
# path to write a pid file after daemonize
|
||||
# * :Host
|
||||
# the host address to bind to (used by supporting Rack::Handler)
|
||||
# * :Port
|
||||
# the port to bind to (used by supporting Rack::Handler)
|
||||
# * :AccessLog
|
||||
# webrick access log options (or supporting Rack::Handler)
|
||||
# * :debug
|
||||
# turn on debug output ($DEBUG = true)
|
||||
# * :warn
|
||||
# turn on warnings ($-w = true)
|
||||
# * :include
|
||||
# add given paths to $LOAD_PATH
|
||||
# * :require
|
||||
# require the given libraries
|
||||
#
|
||||
# Additional options for profiling app initialization include:
|
||||
# * :heapfile
|
||||
# location for ObjectSpace.dump_all to write the output to
|
||||
# * :profile_file
|
||||
# location for CPU/Memory (StackProf) profile output (defaults to a tempfile)
|
||||
# * :profile_mode
|
||||
# StackProf profile mode (cpu|wall|object)
|
||||
def initialize(options = nil)
|
||||
@ignore_options = []
|
||||
|
||||
if options
|
||||
@use_default_options = false
|
||||
@options = options
|
||||
@app = options[:app] if options[:app]
|
||||
else
|
||||
argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV
|
||||
@use_default_options = true
|
||||
@options = parse_options(argv)
|
||||
end
|
||||
end
|
||||
|
||||
def options
|
||||
merged_options = @use_default_options ? default_options.merge(@options) : @options
|
||||
merged_options.reject { |k, v| @ignore_options.include?(k) }
|
||||
end
|
||||
|
||||
def default_options
|
||||
environment = ENV['RACK_ENV'] || 'development'
|
||||
default_host = environment == 'development' ? 'localhost' : '0.0.0.0'
|
||||
|
||||
{
|
||||
environment: environment,
|
||||
pid: nil,
|
||||
Port: 9292,
|
||||
Host: default_host,
|
||||
AccessLog: [],
|
||||
config: "config.ru"
|
||||
}
|
||||
end
|
||||
|
||||
def app
|
||||
@app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
|
||||
end
|
||||
|
||||
class << self
|
||||
def logging_middleware
|
||||
lambda { |server|
|
||||
/CGI/.match?(server.server.name) || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr]
|
||||
}
|
||||
end
|
||||
|
||||
def default_middleware_by_environment
|
||||
m = Hash.new {|h, k| h[k] = []}
|
||||
m["deployment"] = [
|
||||
[Rack::ContentLength],
|
||||
logging_middleware,
|
||||
[Rack::TempfileReaper]
|
||||
]
|
||||
m["development"] = [
|
||||
[Rack::ContentLength],
|
||||
logging_middleware,
|
||||
[Rack::ShowExceptions],
|
||||
[Rack::Lint],
|
||||
[Rack::TempfileReaper]
|
||||
]
|
||||
|
||||
m
|
||||
end
|
||||
|
||||
def middleware
|
||||
default_middleware_by_environment
|
||||
end
|
||||
end
|
||||
|
||||
def middleware
|
||||
self.class.middleware
|
||||
end
|
||||
|
||||
def start(&block)
|
||||
if options[:warn]
|
||||
$-w = true
|
||||
end
|
||||
|
||||
if includes = options[:include]
|
||||
$LOAD_PATH.unshift(*includes)
|
||||
end
|
||||
|
||||
Array(options[:require]).each do |library|
|
||||
require library
|
||||
end
|
||||
|
||||
if options[:debug]
|
||||
$DEBUG = true
|
||||
require 'pp'
|
||||
p options[:server]
|
||||
pp wrapped_app
|
||||
pp app
|
||||
end
|
||||
|
||||
check_pid! if options[:pid]
|
||||
|
||||
# Touch the wrapped app, so that the config.ru is loaded before
|
||||
# daemonization (i.e. before chdir, etc).
|
||||
handle_profiling(options[:heapfile], options[:profile_mode], options[:profile_file]) do
|
||||
wrapped_app
|
||||
end
|
||||
|
||||
daemonize_app if options[:daemonize]
|
||||
|
||||
write_pid if options[:pid]
|
||||
|
||||
trap(:INT) do
|
||||
if server.respond_to?(:shutdown)
|
||||
server.shutdown
|
||||
else
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
server.run(wrapped_app, **options, &block)
|
||||
end
|
||||
|
||||
def server
|
||||
@_server ||= Rack::Handler.get(options[:server])
|
||||
|
||||
unless @_server
|
||||
@_server = Rack::Handler.default
|
||||
|
||||
# We already speak FastCGI
|
||||
@ignore_options = [:File, :Port] if @_server.to_s == 'Rack::Handler::FastCGI'
|
||||
end
|
||||
|
||||
@_server
|
||||
end
|
||||
|
||||
private
|
||||
def build_app_and_options_from_config
|
||||
if !::File.exist? options[:config]
|
||||
abort "configuration #{options[:config]} not found"
|
||||
end
|
||||
|
||||
app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
|
||||
@options.merge!(options) { |key, old, new| old }
|
||||
app
|
||||
end
|
||||
|
||||
def handle_profiling(heapfile, profile_mode, filename)
|
||||
if heapfile
|
||||
require "objspace"
|
||||
ObjectSpace.trace_object_allocations_start
|
||||
yield
|
||||
GC.start
|
||||
::File.open(heapfile, "w") { |f| ObjectSpace.dump_all(output: f) }
|
||||
exit
|
||||
end
|
||||
|
||||
if profile_mode
|
||||
require "stackprof"
|
||||
require "tempfile"
|
||||
|
||||
make_profile_name(filename) do |filename|
|
||||
::File.open(filename, "w") do |f|
|
||||
StackProf.run(mode: profile_mode, out: f) do
|
||||
yield
|
||||
end
|
||||
puts "Profile written to: #{filename}"
|
||||
end
|
||||
end
|
||||
exit
|
||||
end
|
||||
|
||||
yield
|
||||
end
|
||||
|
||||
def make_profile_name(filename)
|
||||
if filename
|
||||
yield filename
|
||||
else
|
||||
::Dir::Tmpname.create("profile.dump") do |tmpname, _, _|
|
||||
yield tmpname
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_app_from_string
|
||||
Rack::Builder.new_from_string(self.options[:builder])
|
||||
end
|
||||
|
||||
def parse_options(args)
|
||||
# Don't evaluate CGI ISINDEX parameters.
|
||||
# http://www.meb.uni-bonn.de/docs/cgi/cl.html
|
||||
args.clear if ENV.include?(REQUEST_METHOD)
|
||||
|
||||
@options = opt_parser.parse!(args)
|
||||
@options[:config] = ::File.expand_path(options[:config])
|
||||
ENV["RACK_ENV"] = options[:environment]
|
||||
@options
|
||||
end
|
||||
|
||||
def opt_parser
|
||||
Options.new
|
||||
end
|
||||
|
||||
def build_app(app)
|
||||
middleware[options[:environment]].reverse_each do |middleware|
|
||||
middleware = middleware.call(self) if middleware.respond_to?(:call)
|
||||
next unless middleware
|
||||
klass, *args = middleware
|
||||
app = klass.new(app, *args)
|
||||
end
|
||||
app
|
||||
end
|
||||
|
||||
def wrapped_app
|
||||
@wrapped_app ||= build_app app
|
||||
end
|
||||
|
||||
def daemonize_app
|
||||
# Cannot be covered as it forks
|
||||
# :nocov:
|
||||
Process.daemon
|
||||
# :nocov:
|
||||
end
|
||||
|
||||
def write_pid
|
||||
::File.open(options[:pid], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") }
|
||||
at_exit { ::FileUtils.rm_f(options[:pid]) }
|
||||
rescue Errno::EEXIST
|
||||
check_pid!
|
||||
retry
|
||||
end
|
||||
|
||||
def check_pid!
|
||||
case pidfile_process_status
|
||||
when :running, :not_owned
|
||||
$stderr.puts "A server is already running. Check #{options[:pid]}."
|
||||
exit(1)
|
||||
when :dead
|
||||
::File.delete(options[:pid])
|
||||
end
|
||||
end
|
||||
|
||||
def pidfile_process_status
|
||||
return :exited unless ::File.exist?(options[:pid])
|
||||
|
||||
pid = ::File.read(options[:pid]).to_i
|
||||
return :dead if pid == 0
|
||||
|
||||
Process.kill(0, pid)
|
||||
:running
|
||||
rescue Errno::ESRCH
|
||||
:dead
|
||||
rescue Errno::EPERM
|
||||
:not_owned
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
523
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb
vendored
Normal file
523
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/abstract/id.rb
vendored
Normal file
@ -0,0 +1,523 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
||||
# bugrep: Andreas Zehnder
|
||||
|
||||
require_relative '../../../rack'
|
||||
require 'time'
|
||||
require 'securerandom'
|
||||
require 'digest/sha2'
|
||||
|
||||
module Rack
|
||||
|
||||
module Session
|
||||
|
||||
class SessionId
|
||||
ID_VERSION = 2
|
||||
|
||||
attr_reader :public_id
|
||||
|
||||
def initialize(public_id)
|
||||
@public_id = public_id
|
||||
end
|
||||
|
||||
def private_id
|
||||
"#{ID_VERSION}::#{hash_sid(public_id)}"
|
||||
end
|
||||
|
||||
alias :cookie_value :public_id
|
||||
alias :to_s :public_id
|
||||
|
||||
def empty?; false; end
|
||||
def inspect; public_id.inspect; end
|
||||
|
||||
private
|
||||
|
||||
def hash_sid(sid)
|
||||
Digest::SHA256.hexdigest(sid)
|
||||
end
|
||||
end
|
||||
|
||||
module Abstract
|
||||
# SessionHash is responsible to lazily load the session from store.
|
||||
|
||||
class SessionHash
|
||||
include Enumerable
|
||||
attr_writer :id
|
||||
|
||||
Unspecified = Object.new
|
||||
|
||||
def self.find(req)
|
||||
req.get_header RACK_SESSION
|
||||
end
|
||||
|
||||
def self.set(req, session)
|
||||
req.set_header RACK_SESSION, session
|
||||
end
|
||||
|
||||
def self.set_options(req, options)
|
||||
req.set_header RACK_SESSION_OPTIONS, options.dup
|
||||
end
|
||||
|
||||
def initialize(store, req)
|
||||
@store = store
|
||||
@req = req
|
||||
@loaded = false
|
||||
end
|
||||
|
||||
def id
|
||||
return @id if @loaded or instance_variable_defined?(:@id)
|
||||
@id = @store.send(:extract_session_id, @req)
|
||||
end
|
||||
|
||||
def options
|
||||
@req.session_options
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
load_for_read!
|
||||
@data.each(&block)
|
||||
end
|
||||
|
||||
def [](key)
|
||||
load_for_read!
|
||||
@data[key.to_s]
|
||||
end
|
||||
|
||||
def dig(key, *keys)
|
||||
load_for_read!
|
||||
@data.dig(key.to_s, *keys)
|
||||
end
|
||||
|
||||
def fetch(key, default = Unspecified, &block)
|
||||
load_for_read!
|
||||
if default == Unspecified
|
||||
@data.fetch(key.to_s, &block)
|
||||
else
|
||||
@data.fetch(key.to_s, default, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def has_key?(key)
|
||||
load_for_read!
|
||||
@data.has_key?(key.to_s)
|
||||
end
|
||||
alias :key? :has_key?
|
||||
alias :include? :has_key?
|
||||
|
||||
def []=(key, value)
|
||||
load_for_write!
|
||||
@data[key.to_s] = value
|
||||
end
|
||||
alias :store :[]=
|
||||
|
||||
def clear
|
||||
load_for_write!
|
||||
@data.clear
|
||||
end
|
||||
|
||||
def destroy
|
||||
clear
|
||||
@id = @store.send(:delete_session, @req, id, options)
|
||||
end
|
||||
|
||||
def to_hash
|
||||
load_for_read!
|
||||
@data.dup
|
||||
end
|
||||
|
||||
def update(hash)
|
||||
load_for_write!
|
||||
@data.update(stringify_keys(hash))
|
||||
end
|
||||
alias :merge! :update
|
||||
|
||||
def replace(hash)
|
||||
load_for_write!
|
||||
@data.replace(stringify_keys(hash))
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
load_for_write!
|
||||
@data.delete(key.to_s)
|
||||
end
|
||||
|
||||
def inspect
|
||||
if loaded?
|
||||
@data.inspect
|
||||
else
|
||||
"#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
|
||||
end
|
||||
end
|
||||
|
||||
def exists?
|
||||
return @exists if instance_variable_defined?(:@exists)
|
||||
@data = {}
|
||||
@exists = @store.send(:session_exists?, @req)
|
||||
end
|
||||
|
||||
def loaded?
|
||||
@loaded
|
||||
end
|
||||
|
||||
def empty?
|
||||
load_for_read!
|
||||
@data.empty?
|
||||
end
|
||||
|
||||
def keys
|
||||
load_for_read!
|
||||
@data.keys
|
||||
end
|
||||
|
||||
def values
|
||||
load_for_read!
|
||||
@data.values
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_for_read!
|
||||
load! if !loaded? && exists?
|
||||
end
|
||||
|
||||
def load_for_write!
|
||||
load! unless loaded?
|
||||
end
|
||||
|
||||
def load!
|
||||
@id, session = @store.send(:load_session, @req)
|
||||
@data = stringify_keys(session)
|
||||
@loaded = true
|
||||
end
|
||||
|
||||
def stringify_keys(other)
|
||||
# Use transform_keys after dropping Ruby 2.4 support
|
||||
hash = {}
|
||||
other.to_hash.each do |key, value|
|
||||
hash[key.to_s] = value
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
# ID sets up a basic framework for implementing an id based sessioning
|
||||
# service. Cookies sent to the client for maintaining sessions will only
|
||||
# contain an id reference. Only #find_session, #write_session and
|
||||
# #delete_session are required to be overwritten.
|
||||
#
|
||||
# All parameters are optional.
|
||||
# * :key determines the name of the cookie, by default it is
|
||||
# 'rack.session'
|
||||
# * :path, :domain, :expire_after, :secure, and :httponly set the related
|
||||
# cookie options as by Rack::Response#set_cookie
|
||||
# * :skip will not a set a cookie in the response nor update the session state
|
||||
# * :defer will not set a cookie in the response but still update the session
|
||||
# state if it is used with a backend
|
||||
# * :renew (implementation dependent) will prompt the generation of a new
|
||||
# session id, and migration of data to be referenced at the new id. If
|
||||
# :defer is set, it will be overridden and the cookie will be set.
|
||||
# * :sidbits sets the number of bits in length that a generated session
|
||||
# id will be.
|
||||
#
|
||||
# These options can be set on a per request basis, at the location of
|
||||
# <tt>env['rack.session.options']</tt>. Additionally the id of the
|
||||
# session can be found within the options hash at the key :id. It is
|
||||
# highly not recommended to change its value.
|
||||
#
|
||||
# Is Rack::Utils::Context compatible.
|
||||
#
|
||||
# Not included by default; you must require 'rack/session/abstract/id'
|
||||
# to use.
|
||||
|
||||
class Persisted
|
||||
DEFAULT_OPTIONS = {
|
||||
key: RACK_SESSION,
|
||||
path: '/',
|
||||
domain: nil,
|
||||
expire_after: nil,
|
||||
secure: false,
|
||||
httponly: true,
|
||||
defer: false,
|
||||
renew: false,
|
||||
sidbits: 128,
|
||||
cookie_only: true,
|
||||
secure_random: ::SecureRandom
|
||||
}.freeze
|
||||
|
||||
attr_reader :key, :default_options, :sid_secure
|
||||
|
||||
def initialize(app, options = {})
|
||||
@app = app
|
||||
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
||||
@key = @default_options.delete(:key)
|
||||
@cookie_only = @default_options.delete(:cookie_only)
|
||||
@same_site = @default_options.delete(:same_site)
|
||||
initialize_sid
|
||||
end
|
||||
|
||||
def call(env)
|
||||
context(env)
|
||||
end
|
||||
|
||||
def context(env, app = @app)
|
||||
req = make_request env
|
||||
prepare_session(req)
|
||||
status, headers, body = app.call(req.env)
|
||||
res = Rack::Response::Raw.new status, headers
|
||||
commit_session(req, res)
|
||||
[status, headers, body]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def make_request(env)
|
||||
Rack::Request.new env
|
||||
end
|
||||
|
||||
def initialize_sid
|
||||
@sidbits = @default_options[:sidbits]
|
||||
@sid_secure = @default_options[:secure_random]
|
||||
@sid_length = @sidbits / 4
|
||||
end
|
||||
|
||||
# Generate a new session id using Ruby #rand. The size of the
|
||||
# session id is controlled by the :sidbits option.
|
||||
# Monkey patch this to use custom methods for session id generation.
|
||||
|
||||
def generate_sid(secure = @sid_secure)
|
||||
if secure
|
||||
secure.hex(@sid_length)
|
||||
else
|
||||
"%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
|
||||
end
|
||||
rescue NotImplementedError
|
||||
generate_sid(false)
|
||||
end
|
||||
|
||||
# Sets the lazy session at 'rack.session' and places options and session
|
||||
# metadata into 'rack.session.options'.
|
||||
|
||||
def prepare_session(req)
|
||||
session_was = req.get_header RACK_SESSION
|
||||
session = session_class.new(self, req)
|
||||
req.set_header RACK_SESSION, session
|
||||
req.set_header RACK_SESSION_OPTIONS, @default_options.dup
|
||||
session.merge! session_was if session_was
|
||||
end
|
||||
|
||||
# Extracts the session id from provided cookies and passes it and the
|
||||
# environment to #find_session.
|
||||
|
||||
def load_session(req)
|
||||
sid = current_session_id(req)
|
||||
sid, session = find_session(req, sid)
|
||||
[sid, session || {}]
|
||||
end
|
||||
|
||||
# Extract session id from request object.
|
||||
|
||||
def extract_session_id(request)
|
||||
sid = request.cookies[@key]
|
||||
sid ||= request.params[@key] unless @cookie_only
|
||||
sid
|
||||
end
|
||||
|
||||
# Returns the current session id from the SessionHash.
|
||||
|
||||
def current_session_id(req)
|
||||
req.get_header(RACK_SESSION).id
|
||||
end
|
||||
|
||||
# Check if the session exists or not.
|
||||
|
||||
def session_exists?(req)
|
||||
value = current_session_id(req)
|
||||
value && !value.empty?
|
||||
end
|
||||
|
||||
# Session should be committed if it was loaded, any of specific options like :renew, :drop
|
||||
# or :expire_after was given and the security permissions match. Skips if skip is given.
|
||||
|
||||
def commit_session?(req, session, options)
|
||||
if options[:skip]
|
||||
false
|
||||
else
|
||||
has_session = loaded_session?(session) || forced_session_update?(session, options)
|
||||
has_session && security_matches?(req, options)
|
||||
end
|
||||
end
|
||||
|
||||
def loaded_session?(session)
|
||||
!session.is_a?(session_class) || session.loaded?
|
||||
end
|
||||
|
||||
def forced_session_update?(session, options)
|
||||
force_options?(options) && session && !session.empty?
|
||||
end
|
||||
|
||||
def force_options?(options)
|
||||
options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any?
|
||||
end
|
||||
|
||||
def security_matches?(request, options)
|
||||
return true unless options[:secure]
|
||||
request.ssl?
|
||||
end
|
||||
|
||||
# Acquires the session from the environment and the session id from
|
||||
# the session options and passes them to #write_session. If successful
|
||||
# and the :defer option is not true, a cookie will be added to the
|
||||
# response with the session's id.
|
||||
|
||||
def commit_session(req, res)
|
||||
session = req.get_header RACK_SESSION
|
||||
options = session.options
|
||||
|
||||
if options[:drop] || options[:renew]
|
||||
session_id = delete_session(req, session.id || generate_sid, options)
|
||||
return unless session_id
|
||||
end
|
||||
|
||||
return unless commit_session?(req, session, options)
|
||||
|
||||
session.send(:load!) unless loaded_session?(session)
|
||||
session_id ||= session.id
|
||||
session_data = session.to_hash.delete_if { |k, v| v.nil? }
|
||||
|
||||
if not data = write_session(req, session_id, session_data, options)
|
||||
req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.")
|
||||
elsif options[:defer] and not options[:renew]
|
||||
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
|
||||
else
|
||||
cookie = Hash.new
|
||||
cookie[:value] = cookie_value(data)
|
||||
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
||||
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
||||
|
||||
if @same_site.respond_to? :call
|
||||
cookie[:same_site] = @same_site.call(req, res)
|
||||
else
|
||||
cookie[:same_site] = @same_site
|
||||
end
|
||||
set_cookie(req, res, cookie.merge!(options))
|
||||
end
|
||||
end
|
||||
public :commit_session
|
||||
|
||||
def cookie_value(data)
|
||||
data
|
||||
end
|
||||
|
||||
# Sets the cookie back to the client with session id. We skip the cookie
|
||||
# setting if the value didn't change (sid is the same) or expires was given.
|
||||
|
||||
def set_cookie(request, res, cookie)
|
||||
if request.cookies[@key] != cookie[:value] || cookie[:expires]
|
||||
res.set_cookie_header =
|
||||
Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie)
|
||||
end
|
||||
end
|
||||
|
||||
# Allow subclasses to prepare_session for different Session classes
|
||||
|
||||
def session_class
|
||||
SessionHash
|
||||
end
|
||||
|
||||
# All thread safety and session retrieval procedures should occur here.
|
||||
# Should return [session_id, session].
|
||||
# If nil is provided as the session id, generation of a new valid id
|
||||
# should occur within.
|
||||
|
||||
def find_session(env, sid)
|
||||
raise '#find_session not implemented.'
|
||||
end
|
||||
|
||||
# All thread safety and session storage procedures should occur here.
|
||||
# Must return the session id if the session was saved successfully, or
|
||||
# false if the session could not be saved.
|
||||
|
||||
def write_session(req, sid, session, options)
|
||||
raise '#write_session not implemented.'
|
||||
end
|
||||
|
||||
# All thread safety and session destroy procedures should occur here.
|
||||
# Should return a new session id or nil if options[:drop]
|
||||
|
||||
def delete_session(req, sid, options)
|
||||
raise '#delete_session not implemented'
|
||||
end
|
||||
end
|
||||
|
||||
class PersistedSecure < Persisted
|
||||
class SecureSessionHash < SessionHash
|
||||
def [](key)
|
||||
if key == "session_id"
|
||||
load_for_read!
|
||||
id.public_id if id
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def generate_sid(*)
|
||||
public_id = super
|
||||
|
||||
SessionId.new(public_id)
|
||||
end
|
||||
|
||||
def extract_session_id(*)
|
||||
public_id = super
|
||||
public_id && SessionId.new(public_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def session_class
|
||||
SecureSessionHash
|
||||
end
|
||||
|
||||
def cookie_value(data)
|
||||
data.cookie_value
|
||||
end
|
||||
end
|
||||
|
||||
class ID < Persisted
|
||||
def self.inherited(klass)
|
||||
k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
|
||||
unless k.instance_variable_defined?(:"@_rack_warned")
|
||||
warn "#{klass} is inheriting from #{ID}. Inheriting from #{ID} is deprecated, please inherit from #{Persisted} instead" if $VERBOSE
|
||||
k.instance_variable_set(:"@_rack_warned", true)
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
# All thread safety and session retrieval procedures should occur here.
|
||||
# Should return [session_id, session].
|
||||
# If nil is provided as the session id, generation of a new valid id
|
||||
# should occur within.
|
||||
|
||||
def find_session(req, sid)
|
||||
get_session req.env, sid
|
||||
end
|
||||
|
||||
# All thread safety and session storage procedures should occur here.
|
||||
# Must return the session id if the session was saved successfully, or
|
||||
# false if the session could not be saved.
|
||||
|
||||
def write_session(req, sid, session, options)
|
||||
set_session req.env, sid, session, options
|
||||
end
|
||||
|
||||
# All thread safety and session destroy procedures should occur here.
|
||||
# Should return a new session id or nil if options[:drop]
|
||||
|
||||
def delete_session(req, sid, options)
|
||||
destroy_session req.env, sid, options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
203
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/cookie.rb
vendored
Normal file
203
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/cookie.rb
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'openssl'
|
||||
require 'zlib'
|
||||
require_relative 'abstract/id'
|
||||
require 'json'
|
||||
require 'base64'
|
||||
|
||||
module Rack
|
||||
|
||||
module Session
|
||||
|
||||
# Rack::Session::Cookie provides simple cookie based session management.
|
||||
# By default, the session is a Ruby Hash stored as base64 encoded marshalled
|
||||
# data set to :key (default: rack.session). The object that encodes the
|
||||
# session data is configurable and must respond to +encode+ and +decode+.
|
||||
# Both methods must take a string and return a string.
|
||||
#
|
||||
# When the secret key is set, cookie data is checked for data integrity.
|
||||
# The old secret key is also accepted and allows graceful secret rotation.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# use Rack::Session::Cookie, :key => 'rack.session',
|
||||
# :domain => 'foo.com',
|
||||
# :path => '/',
|
||||
# :expire_after => 2592000,
|
||||
# :secret => 'change_me',
|
||||
# :old_secret => 'also_change_me'
|
||||
#
|
||||
# All parameters are optional.
|
||||
#
|
||||
# Example of a cookie with no encoding:
|
||||
#
|
||||
# Rack::Session::Cookie.new(application, {
|
||||
# :coder => Rack::Session::Cookie::Identity.new
|
||||
# })
|
||||
#
|
||||
# Example of a cookie with custom encoding:
|
||||
#
|
||||
# Rack::Session::Cookie.new(application, {
|
||||
# :coder => Class.new {
|
||||
# def encode(str); str.reverse; end
|
||||
# def decode(str); str.reverse; end
|
||||
# }.new
|
||||
# })
|
||||
#
|
||||
|
||||
class Cookie < Abstract::PersistedSecure
|
||||
# Encode session cookies as Base64
|
||||
class Base64
|
||||
def encode(str)
|
||||
::Base64.strict_encode64(str)
|
||||
end
|
||||
|
||||
def decode(str)
|
||||
::Base64.decode64(str)
|
||||
end
|
||||
|
||||
# Encode session cookies as Marshaled Base64 data
|
||||
class Marshal < Base64
|
||||
def encode(str)
|
||||
super(::Marshal.dump(str))
|
||||
end
|
||||
|
||||
def decode(str)
|
||||
return unless str
|
||||
::Marshal.load(super(str)) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
# N.B. Unlike other encoding methods, the contained objects must be a
|
||||
# valid JSON composite type, either a Hash or an Array.
|
||||
class JSON < Base64
|
||||
def encode(obj)
|
||||
super(::JSON.dump(obj))
|
||||
end
|
||||
|
||||
def decode(str)
|
||||
return unless str
|
||||
::JSON.parse(super(str)) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
class ZipJSON < Base64
|
||||
def encode(obj)
|
||||
super(Zlib::Deflate.deflate(::JSON.dump(obj)))
|
||||
end
|
||||
|
||||
def decode(str)
|
||||
return unless str
|
||||
::JSON.parse(Zlib::Inflate.inflate(super(str)))
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Use no encoding for session cookies
|
||||
class Identity
|
||||
def encode(str); str; end
|
||||
def decode(str); str; end
|
||||
end
|
||||
|
||||
attr_reader :coder
|
||||
|
||||
def initialize(app, options = {})
|
||||
@secrets = options.values_at(:secret, :old_secret).compact
|
||||
@hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1)
|
||||
|
||||
warn <<-MSG unless secure?(options)
|
||||
SECURITY WARNING: No secret option provided to Rack::Session::Cookie.
|
||||
This poses a security threat. It is strongly recommended that you
|
||||
provide a secret to prevent exploits that may be possible from crafted
|
||||
cookies. This will not be supported in future versions of Rack, and
|
||||
future versions will even invalidate your existing user cookies.
|
||||
|
||||
Called from: #{caller[0]}.
|
||||
MSG
|
||||
@coder = options[:coder] ||= Base64::Marshal.new
|
||||
super(app, options.merge!(cookie_only: true))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_session(req, sid)
|
||||
data = unpacked_cookie_data(req)
|
||||
data = persistent_session_id!(data)
|
||||
[data["session_id"], data]
|
||||
end
|
||||
|
||||
def extract_session_id(request)
|
||||
unpacked_cookie_data(request)["session_id"]
|
||||
end
|
||||
|
||||
def unpacked_cookie_data(request)
|
||||
request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
|
||||
session_data = request.cookies[@key]
|
||||
|
||||
if @secrets.size > 0 && session_data
|
||||
session_data, _, digest = session_data.rpartition('--')
|
||||
session_data = nil unless digest_match?(session_data, digest)
|
||||
end
|
||||
|
||||
request.set_header(k, coder.decode(session_data) || {})
|
||||
end
|
||||
end
|
||||
|
||||
def persistent_session_id!(data, sid = nil)
|
||||
data ||= {}
|
||||
data["session_id"] ||= sid || generate_sid
|
||||
data
|
||||
end
|
||||
|
||||
class SessionId < DelegateClass(Session::SessionId)
|
||||
attr_reader :cookie_value
|
||||
|
||||
def initialize(session_id, cookie_value)
|
||||
super(session_id)
|
||||
@cookie_value = cookie_value
|
||||
end
|
||||
end
|
||||
|
||||
def write_session(req, session_id, session, options)
|
||||
session = session.merge("session_id" => session_id)
|
||||
session_data = coder.encode(session)
|
||||
|
||||
if @secrets.first
|
||||
session_data << "--#{generate_hmac(session_data, @secrets.first)}"
|
||||
end
|
||||
|
||||
if session_data.size > (4096 - @key.size)
|
||||
req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
|
||||
nil
|
||||
else
|
||||
SessionId.new(session_id, session_data)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_session(req, session_id, options)
|
||||
# Nothing to do here, data is in the client
|
||||
generate_sid unless options[:drop]
|
||||
end
|
||||
|
||||
def digest_match?(data, digest)
|
||||
return unless data && digest
|
||||
@secrets.any? do |secret|
|
||||
Rack::Utils.secure_compare(digest, generate_hmac(data, secret))
|
||||
end
|
||||
end
|
||||
|
||||
def generate_hmac(data, secret)
|
||||
OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
|
||||
end
|
||||
|
||||
def secure?(options)
|
||||
@secrets.size >= 1 ||
|
||||
(options[:coder] && options[:let_coder_handle_secure_encoding])
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
10
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/memcache.rb
vendored
Normal file
10
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/memcache.rb
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rack/session/dalli'
|
||||
|
||||
module Rack
|
||||
module Session
|
||||
warn "Rack::Session::Memcache is deprecated, please use Rack::Session::Dalli from 'dalli' gem instead."
|
||||
Memcache = Dalli
|
||||
end
|
||||
end
|
||||
85
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/pool.rb
vendored
Normal file
85
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/session/pool.rb
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
||||
# THANKS:
|
||||
# apeiros, for session id generation, expiry setup, and threadiness
|
||||
# sergio, threadiness and bugreps
|
||||
|
||||
require_relative 'abstract/id'
|
||||
require 'thread'
|
||||
|
||||
module Rack
|
||||
module Session
|
||||
# Rack::Session::Pool provides simple cookie based session management.
|
||||
# Session data is stored in a hash held by @pool.
|
||||
# In the context of a multithreaded environment, sessions being
|
||||
# committed to the pool is done in a merging manner.
|
||||
#
|
||||
# The :drop option is available in rack.session.options if you wish to
|
||||
# explicitly remove the session from the session cache.
|
||||
#
|
||||
# Example:
|
||||
# myapp = MyRackApp.new
|
||||
# sessioned = Rack::Session::Pool.new(myapp,
|
||||
# :domain => 'foo.com',
|
||||
# :expire_after => 2592000
|
||||
# )
|
||||
# Rack::Handler::WEBrick.run sessioned
|
||||
|
||||
class Pool < Abstract::PersistedSecure
|
||||
attr_reader :mutex, :pool
|
||||
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge drop: false
|
||||
|
||||
def initialize(app, options = {})
|
||||
super
|
||||
@pool = Hash.new
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
def generate_sid
|
||||
loop do
|
||||
sid = super
|
||||
break sid unless @pool.key? sid.private_id
|
||||
end
|
||||
end
|
||||
|
||||
def find_session(req, sid)
|
||||
with_lock(req) do
|
||||
unless sid and session = get_session_with_fallback(sid)
|
||||
sid, session = generate_sid, {}
|
||||
@pool.store sid.private_id, session
|
||||
end
|
||||
[sid, session]
|
||||
end
|
||||
end
|
||||
|
||||
def write_session(req, session_id, new_session, options)
|
||||
with_lock(req) do
|
||||
@pool.store session_id.private_id, new_session
|
||||
session_id
|
||||
end
|
||||
end
|
||||
|
||||
def delete_session(req, session_id, options)
|
||||
with_lock(req) do
|
||||
@pool.delete(session_id.public_id)
|
||||
@pool.delete(session_id.private_id)
|
||||
generate_sid unless options[:drop]
|
||||
end
|
||||
end
|
||||
|
||||
def with_lock(req)
|
||||
@mutex.lock if req.multithread?
|
||||
yield
|
||||
ensure
|
||||
@mutex.unlock if @mutex.locked?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_session_with_fallback(sid)
|
||||
@pool[sid.private_id] || @pool[sid.public_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
390
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/show_exceptions.rb
vendored
Normal file
390
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/show_exceptions.rb
vendored
Normal file
@ -0,0 +1,390 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'ostruct'
|
||||
require 'erb'
|
||||
|
||||
module Rack
|
||||
# Rack::ShowExceptions catches all exceptions raised from the app it
|
||||
# wraps. It shows a useful backtrace with the sourcefile and
|
||||
# clickable context, the whole Rack environment and the request
|
||||
# data.
|
||||
#
|
||||
# Be careful when you use this on public-facing sites as it could
|
||||
# reveal information helpful to attackers.
|
||||
|
||||
class ShowExceptions
|
||||
CONTEXT = 7
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@app.call(env)
|
||||
rescue StandardError, LoadError, SyntaxError => e
|
||||
exception_string = dump_exception(e)
|
||||
|
||||
env[RACK_ERRORS].puts(exception_string)
|
||||
env[RACK_ERRORS].flush
|
||||
|
||||
if accepts_html?(env)
|
||||
content_type = "text/html"
|
||||
body = pretty(env, e)
|
||||
else
|
||||
content_type = "text/plain"
|
||||
body = exception_string
|
||||
end
|
||||
|
||||
[
|
||||
500,
|
||||
{
|
||||
CONTENT_TYPE => content_type,
|
||||
CONTENT_LENGTH => body.bytesize.to_s,
|
||||
},
|
||||
[body],
|
||||
]
|
||||
end
|
||||
|
||||
def prefers_plaintext?(env)
|
||||
!accepts_html?(env)
|
||||
end
|
||||
|
||||
def accepts_html?(env)
|
||||
Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html])
|
||||
end
|
||||
private :accepts_html?
|
||||
|
||||
def dump_exception(exception)
|
||||
string = "#{exception.class}: #{exception.message}\n".dup
|
||||
string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
|
||||
string
|
||||
end
|
||||
|
||||
def pretty(env, exception)
|
||||
req = Rack::Request.new(env)
|
||||
|
||||
# This double assignment is to prevent an "unused variable" warning.
|
||||
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
||||
path = path = (req.script_name + req.path_info).squeeze("/")
|
||||
|
||||
# This double assignment is to prevent an "unused variable" warning.
|
||||
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
||||
frames = frames = exception.backtrace.map { |line|
|
||||
frame = OpenStruct.new
|
||||
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
||||
frame.filename = $1
|
||||
frame.lineno = $2.to_i
|
||||
frame.function = $4
|
||||
|
||||
begin
|
||||
lineno = frame.lineno - 1
|
||||
lines = ::File.readlines(frame.filename)
|
||||
frame.pre_context_lineno = [lineno - CONTEXT, 0].max
|
||||
frame.pre_context = lines[frame.pre_context_lineno...lineno]
|
||||
frame.context_line = lines[lineno].chomp
|
||||
frame.post_context_lineno = [lineno + CONTEXT, lines.size].min
|
||||
frame.post_context = lines[lineno + 1..frame.post_context_lineno]
|
||||
rescue
|
||||
end
|
||||
|
||||
frame
|
||||
else
|
||||
nil
|
||||
end
|
||||
}.compact
|
||||
|
||||
template.result(binding)
|
||||
end
|
||||
|
||||
def template
|
||||
TEMPLATE
|
||||
end
|
||||
|
||||
def h(obj) # :nodoc:
|
||||
case obj
|
||||
when String
|
||||
Utils.escape_html(obj)
|
||||
else
|
||||
Utils.escape_html(obj.inspect)
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
# adapted from Django <www.djangoproject.com>
|
||||
# Copyright (c) Django Software Foundation and individual contributors.
|
||||
# Used under the modified BSD license:
|
||||
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
||||
TEMPLATE = ERB.new(<<-'HTML'.gsub(/^ /, ''))
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||
<title><%=h exception.class %> at <%=h path %></title>
|
||||
<style type="text/css">
|
||||
html * { padding:0; margin:0; }
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; }
|
||||
h2 { margin-bottom:.8em; }
|
||||
h2 span { font-size:80%; color:#666; font-weight:normal; }
|
||||
h3 { margin:1em 0 .5em 0; }
|
||||
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
||||
table {
|
||||
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
||||
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
||||
thead th {
|
||||
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
||||
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
||||
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
||||
table.vars { margin:5px 0 2px 40px; }
|
||||
table.vars td, table.req td { font-family:monospace; }
|
||||
table td.code { width:100%;}
|
||||
table td.code div { overflow:hidden; }
|
||||
table.source th { color:#666; }
|
||||
table.source td {
|
||||
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
||||
ul.traceback { list-style-type:none; }
|
||||
ul.traceback li.frame { margin-bottom:1em; }
|
||||
div.context { margin: 10px 0; }
|
||||
div.context ol {
|
||||
padding-left:30px; margin:0 10px; list-style-position: inside; }
|
||||
div.context ol li {
|
||||
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
|
||||
div.context ol.context-line li { color:black; background-color:#ccc; }
|
||||
div.context ol.context-line li span { float: right; }
|
||||
div.commands { margin-left: 40px; }
|
||||
div.commands a { color:black; text-decoration:none; }
|
||||
#summary { background: #ffc; }
|
||||
#summary h2 { font-weight: normal; color: #666; }
|
||||
#summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
|
||||
#summary ul#quicklinks li { float: left; padding: 0 1em; }
|
||||
#summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
|
||||
#explanation { background:#eee; }
|
||||
#template, #template-not-exist { background:#f6f6f6; }
|
||||
#template-not-exist ul { margin: 0 0 0 20px; }
|
||||
#traceback { background:#eee; }
|
||||
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
||||
#summary table { border:none; background:transparent; }
|
||||
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
||||
#requestinfo h3 { margin-bottom:-1em; }
|
||||
.error { background: #ffc; }
|
||||
.specific { color:#cc3300; font-weight:bold; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
//<!--
|
||||
function getElementsByClassName(oElm, strTagName, strClassName){
|
||||
// Written by Jonathan Snook, http://www.snook.ca/jon;
|
||||
// Add-ons by Robert Nyman, http://www.robertnyman.com
|
||||
var arrElements = (strTagName == "*" && document.all)? document.all :
|
||||
oElm.getElementsByTagName(strTagName);
|
||||
var arrReturnElements = new Array();
|
||||
strClassName = strClassName.replace(/\-/g, "\\-");
|
||||
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
|
||||
var oElement;
|
||||
for(var i=0; i<arrElements.length; i++){
|
||||
oElement = arrElements[i];
|
||||
if(oRegExp.test(oElement.className)){
|
||||
arrReturnElements.push(oElement);
|
||||
}
|
||||
}
|
||||
return (arrReturnElements)
|
||||
}
|
||||
function hideAll(elems) {
|
||||
for (var e = 0; e < elems.length; e++) {
|
||||
elems[e].style.display = 'none';
|
||||
}
|
||||
}
|
||||
window.onload = function() {
|
||||
hideAll(getElementsByClassName(document, 'table', 'vars'));
|
||||
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
|
||||
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
|
||||
}
|
||||
function toggle() {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var e = document.getElementById(arguments[i]);
|
||||
if (e) {
|
||||
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function varToggle(link, id) {
|
||||
toggle('v' + id);
|
||||
var s = link.getElementsByTagName('span')[0];
|
||||
var uarr = String.fromCharCode(0x25b6);
|
||||
var darr = String.fromCharCode(0x25bc);
|
||||
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
|
||||
return false;
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="summary">
|
||||
<h1><%=h exception.class %> at <%=h path %></h1>
|
||||
<h2><%=h exception.message %></h2>
|
||||
<table><tr>
|
||||
<th>Ruby</th>
|
||||
<td>
|
||||
<% if first = frames.first %>
|
||||
<code><%=h first.filename %></code>: in <code><%=h first.function %></code>, line <%=h frames.first.lineno %>
|
||||
<% else %>
|
||||
unknown location
|
||||
<% end %>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<th>Web</th>
|
||||
<td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
|
||||
</tr></table>
|
||||
|
||||
<h3>Jump to:</h3>
|
||||
<ul id="quicklinks">
|
||||
<li><a href="#get-info">GET</a></li>
|
||||
<li><a href="#post-info">POST</a></li>
|
||||
<li><a href="#cookie-info">Cookies</a></li>
|
||||
<li><a href="#env-info">ENV</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="traceback">
|
||||
<h2>Traceback <span>(innermost first)</span></h2>
|
||||
<ul class="traceback">
|
||||
<% frames.each { |frame| %>
|
||||
<li class="frame">
|
||||
<code><%=h frame.filename %></code>: in <code><%=h frame.function %></code>
|
||||
|
||||
<% if frame.context_line %>
|
||||
<div class="context" id="c<%=h frame.object_id %>">
|
||||
<% if frame.pre_context %>
|
||||
<ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
|
||||
<% frame.pre_context.each { |line| %>
|
||||
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
|
||||
<% } %>
|
||||
</ol>
|
||||
<% end %>
|
||||
|
||||
<ol start="<%=h frame.lineno %>" class="context-line">
|
||||
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %><span>...</span></li></ol>
|
||||
|
||||
<% if frame.post_context %>
|
||||
<ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
|
||||
<% frame.post_context.each { |line| %>
|
||||
<li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
|
||||
<% } %>
|
||||
</ol>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="requestinfo">
|
||||
<h2>Request information</h2>
|
||||
|
||||
<h3 id="get-info">GET</h3>
|
||||
<% if req.GET and not req.GET.empty? %>
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
||||
<tr>
|
||||
<td><%=h key %></td>
|
||||
<td class="code"><div><%=h val.inspect %></div></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p>No GET data.</p>
|
||||
<% end %>
|
||||
|
||||
<h3 id="post-info">POST</h3>
|
||||
<% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
||||
<tr>
|
||||
<td><%=h key %></td>
|
||||
<td class="code"><div><%=h val.inspect %></div></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p><%= no_post_data || "No POST data" %>.</p>
|
||||
<% end %>
|
||||
|
||||
|
||||
<h3 id="cookie-info">COOKIES</h3>
|
||||
<% unless req.cookies.empty? %>
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% req.cookies.each { |key, val| %>
|
||||
<tr>
|
||||
<td><%=h key %></td>
|
||||
<td class="code"><div><%=h val.inspect %></div></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p>No cookie data.</p>
|
||||
<% end %>
|
||||
|
||||
<h3 id="env-info">Rack ENV</h3>
|
||||
<table class="req">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
||||
<tr>
|
||||
<td><%=h key %></td>
|
||||
<td class="code"><div><%=h val.inspect %></div></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="explanation">
|
||||
<p>
|
||||
You're seeing this error because you use <code>Rack::ShowExceptions</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
||||
113
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/show_status.rb
vendored
Normal file
113
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/show_status.rb
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'erb'
|
||||
|
||||
module Rack
|
||||
# Rack::ShowStatus catches all empty responses and replaces them
|
||||
# with a site explaining the error.
|
||||
#
|
||||
# Additional details can be put into <tt>rack.showstatus.detail</tt>
|
||||
# and will be shown as HTML. If such details exist, the error page
|
||||
# is always rendered, even if the reply was not empty.
|
||||
|
||||
class ShowStatus
|
||||
def initialize(app)
|
||||
@app = app
|
||||
@template = ERB.new(TEMPLATE)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
status, headers, body = @app.call(env)
|
||||
headers = Utils::HeaderHash[headers]
|
||||
empty = headers[CONTENT_LENGTH].to_i <= 0
|
||||
|
||||
# client or server error, or explicit message
|
||||
if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL]
|
||||
# This double assignment is to prevent an "unused variable" warning.
|
||||
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
||||
req = req = Rack::Request.new(env)
|
||||
|
||||
message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
|
||||
|
||||
# This double assignment is to prevent an "unused variable" warning.
|
||||
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
||||
detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message
|
||||
|
||||
body = @template.result(binding)
|
||||
size = body.bytesize
|
||||
[status, headers.merge(CONTENT_TYPE => "text/html", CONTENT_LENGTH => size.to_s), [body]]
|
||||
else
|
||||
[status, headers, body]
|
||||
end
|
||||
end
|
||||
|
||||
def h(obj) # :nodoc:
|
||||
case obj
|
||||
when String
|
||||
Utils.escape_html(obj)
|
||||
else
|
||||
Utils.escape_html(obj.inspect)
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
# adapted from Django <www.djangoproject.com>
|
||||
# Copyright (c) Django Software Foundation and individual contributors.
|
||||
# Used under the modified BSD license:
|
||||
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
||||
TEMPLATE = <<'HTML'
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<title><%=h message %> at <%=h req.script_name + req.path_info %></title>
|
||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||
<style type="text/css">
|
||||
html * { padding:0; margin:0; }
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; background:#eee; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; margin-bottom:.4em; }
|
||||
h1 span { font-size:60%; color:#666; font-weight:normal; }
|
||||
table { border:none; border-collapse: collapse; width:100%; }
|
||||
td, th { vertical-align:top; padding:2px 3px; }
|
||||
th { width:12em; text-align:right; color:#666; padding-right:.5em; }
|
||||
#info { background:#f6f6f6; }
|
||||
#info ol { margin: 0.5em 4em; }
|
||||
#info ol li { font-family: monospace; }
|
||||
#summary { background: #ffc; }
|
||||
#explanation { background:#eee; border-bottom: 0px none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="summary">
|
||||
<h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
|
||||
<table class="meta">
|
||||
<tr>
|
||||
<th>Request Method:</th>
|
||||
<td><%=h req.request_method %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Request URL:</th>
|
||||
<td><%=h req.url %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="info">
|
||||
<p><%=h detail %></p>
|
||||
</div>
|
||||
|
||||
<div id="explanation">
|
||||
<p>
|
||||
You're seeing this error because you use <code>Rack::ShowStatus</code>.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
||||
187
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/static.rb
vendored
Normal file
187
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/static.rb
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
|
||||
# The Rack::Static middleware intercepts requests for static files
|
||||
# (javascript files, images, stylesheets, etc) based on the url prefixes or
|
||||
# route mappings passed in the options, and serves them using a Rack::Files
|
||||
# object. This allows a Rack stack to serve both static and dynamic content.
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# Serve all requests beginning with /media from the "media" folder located
|
||||
# in the current directory (ie media/*):
|
||||
#
|
||||
# use Rack::Static, :urls => ["/media"]
|
||||
#
|
||||
# Same as previous, but instead of returning 404 for missing files under
|
||||
# /media, call the next middleware:
|
||||
#
|
||||
# use Rack::Static, :urls => ["/media"], :cascade => true
|
||||
#
|
||||
# Serve all requests beginning with /css or /images from the folder "public"
|
||||
# in the current directory (ie public/css/* and public/images/*):
|
||||
#
|
||||
# use Rack::Static, :urls => ["/css", "/images"], :root => "public"
|
||||
#
|
||||
# Serve all requests to / with "index.html" from the folder "public" in the
|
||||
# current directory (ie public/index.html):
|
||||
#
|
||||
# use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
|
||||
#
|
||||
# Serve all requests normally from the folder "public" in the current
|
||||
# directory but uses index.html as default route for "/"
|
||||
#
|
||||
# use Rack::Static, :urls => [""], :root => 'public', :index =>
|
||||
# 'index.html'
|
||||
#
|
||||
# Set custom HTTP Headers for based on rules:
|
||||
#
|
||||
# use Rack::Static, :root => 'public',
|
||||
# :header_rules => [
|
||||
# [rule, {header_field => content, header_field => content}],
|
||||
# [rule, {header_field => content}]
|
||||
# ]
|
||||
#
|
||||
# Rules for selecting files:
|
||||
#
|
||||
# 1) All files
|
||||
# Provide the :all symbol
|
||||
# :all => Matches every file
|
||||
#
|
||||
# 2) Folders
|
||||
# Provide the folder path as a string
|
||||
# '/folder' or '/folder/subfolder' => Matches files in a certain folder
|
||||
#
|
||||
# 3) File Extensions
|
||||
# Provide the file extensions as an array
|
||||
# ['css', 'js'] or %w(css js) => Matches files ending in .css or .js
|
||||
#
|
||||
# 4) Regular Expressions / Regexp
|
||||
# Provide a regular expression
|
||||
# %r{\.(?:css|js)\z} => Matches files ending in .css or .js
|
||||
# /\.(?:eot|ttf|otf|woff2|woff|svg)\z/ => Matches files ending in
|
||||
# the most common web font formats (.eot, .ttf, .otf, .woff2, .woff, .svg)
|
||||
# Note: This Regexp is available as a shortcut, using the :fonts rule
|
||||
#
|
||||
# 5) Font Shortcut
|
||||
# Provide the :fonts symbol
|
||||
# :fonts => Uses the Regexp rule stated right above to match all common web font endings
|
||||
#
|
||||
# Rule Ordering:
|
||||
# Rules are applied in the order that they are provided.
|
||||
# List rather general rules above special ones.
|
||||
#
|
||||
# Complete example use case including HTTP header rules:
|
||||
#
|
||||
# use Rack::Static, :root => 'public',
|
||||
# :header_rules => [
|
||||
# # Cache all static files in public caches (e.g. Rack::Cache)
|
||||
# # as well as in the browser
|
||||
# [:all, {'Cache-Control' => 'public, max-age=31536000'}],
|
||||
#
|
||||
# # Provide web fonts with cross-origin access-control-headers
|
||||
# # Firefox requires this when serving assets using a Content Delivery Network
|
||||
# [:fonts, {'Access-Control-Allow-Origin' => '*'}]
|
||||
# ]
|
||||
#
|
||||
class Static
|
||||
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
def initialize(app, options = {})
|
||||
@app = app
|
||||
@urls = options[:urls] || ["/favicon.ico"]
|
||||
@index = options[:index]
|
||||
@gzip = options[:gzip]
|
||||
@cascade = options[:cascade]
|
||||
root = options[:root] || Dir.pwd
|
||||
|
||||
# HTTP Headers
|
||||
@header_rules = options[:header_rules] || []
|
||||
# Allow for legacy :cache_control option while prioritizing global header_rules setting
|
||||
@header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control]
|
||||
|
||||
@file_server = Rack::Files.new(root)
|
||||
end
|
||||
|
||||
def add_index_root?(path)
|
||||
@index && route_file(path) && path.end_with?('/')
|
||||
end
|
||||
|
||||
def overwrite_file_path(path)
|
||||
@urls.kind_of?(Hash) && @urls.key?(path) || add_index_root?(path)
|
||||
end
|
||||
|
||||
def route_file(path)
|
||||
@urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 }
|
||||
end
|
||||
|
||||
def can_serve(path)
|
||||
route_file(path) || overwrite_file_path(path)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
path = env[PATH_INFO]
|
||||
|
||||
if can_serve(path)
|
||||
if overwrite_file_path(path)
|
||||
env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
|
||||
elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
|
||||
path = env[PATH_INFO]
|
||||
env[PATH_INFO] += '.gz'
|
||||
response = @file_server.call(env)
|
||||
env[PATH_INFO] = path
|
||||
|
||||
if response[0] == 404
|
||||
response = nil
|
||||
elsif response[0] == 304
|
||||
# Do nothing, leave headers as is
|
||||
else
|
||||
if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
|
||||
response[1][CONTENT_TYPE] = mime_type
|
||||
end
|
||||
response[1]['Content-Encoding'] = 'gzip'
|
||||
end
|
||||
end
|
||||
|
||||
path = env[PATH_INFO]
|
||||
response ||= @file_server.call(env)
|
||||
|
||||
if @cascade && response[0] == 404
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
headers = response[1]
|
||||
applicable_rules(path).each do |rule, new_headers|
|
||||
new_headers.each { |field, content| headers[field] = content }
|
||||
end
|
||||
|
||||
response
|
||||
else
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
# Convert HTTP header rules to HTTP headers
|
||||
def applicable_rules(path)
|
||||
@header_rules.find_all do |rule, new_headers|
|
||||
case rule
|
||||
when :all
|
||||
true
|
||||
when :fonts
|
||||
/\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
|
||||
when String
|
||||
path = ::Rack::Utils.unescape(path)
|
||||
path.start_with?(rule) || path.start_with?('/' + rule)
|
||||
when Array
|
||||
/\.(#{rule.join('|')})\z/.match?(path)
|
||||
when Regexp
|
||||
rule.match?(path)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
22
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb
vendored
Normal file
22
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rack
|
||||
|
||||
# Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart)
|
||||
# Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter
|
||||
# https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ
|
||||
class TempfileReaper
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
env[RACK_TEMPFILES] ||= []
|
||||
status, headers, body = @app.call(env)
|
||||
body_proxy = BodyProxy.new(body) do
|
||||
env[RACK_TEMPFILES].each(&:close!) unless env[RACK_TEMPFILES].nil?
|
||||
end
|
||||
[status, headers, body_proxy]
|
||||
end
|
||||
end
|
||||
end
|
||||
97
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/urlmap.rb
vendored
Normal file
97
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/urlmap.rb
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'set'
|
||||
|
||||
module Rack
|
||||
# Rack::URLMap takes a hash mapping urls or paths to apps, and
|
||||
# dispatches accordingly. Support for HTTP/1.1 host names exists if
|
||||
# the URLs start with <tt>http://</tt> or <tt>https://</tt>.
|
||||
#
|
||||
# URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
|
||||
# relevant for dispatch is in the SCRIPT_NAME, and the rest in the
|
||||
# PATH_INFO. This should be taken care of when you need to
|
||||
# reconstruct the URL in order to create links.
|
||||
#
|
||||
# URLMap dispatches in such a way that the longest paths are tried
|
||||
# first, since they are most specific.
|
||||
|
||||
class URLMap
|
||||
def initialize(map = {})
|
||||
remap(map)
|
||||
end
|
||||
|
||||
def remap(map)
|
||||
@known_hosts = Set[]
|
||||
@mapping = map.map { |location, app|
|
||||
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
||||
host, location = $1, $2
|
||||
@known_hosts << host
|
||||
else
|
||||
host = nil
|
||||
end
|
||||
|
||||
unless location[0] == ?/
|
||||
raise ArgumentError, "paths need to start with /"
|
||||
end
|
||||
|
||||
location = location.chomp('/')
|
||||
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
|
||||
|
||||
[host, location, match, app]
|
||||
}.sort_by do |(host, location, _, _)|
|
||||
[host ? -host.size : Float::INFINITY, -location.size]
|
||||
end
|
||||
end
|
||||
|
||||
def call(env)
|
||||
path = env[PATH_INFO]
|
||||
script_name = env[SCRIPT_NAME]
|
||||
http_host = env[HTTP_HOST]
|
||||
server_name = env[SERVER_NAME]
|
||||
server_port = env[SERVER_PORT]
|
||||
|
||||
is_same_server = casecmp?(http_host, server_name) ||
|
||||
casecmp?(http_host, "#{server_name}:#{server_port}")
|
||||
|
||||
is_host_known = @known_hosts.include? http_host
|
||||
|
||||
@mapping.each do |host, location, match, app|
|
||||
unless casecmp?(http_host, host) \
|
||||
|| casecmp?(server_name, host) \
|
||||
|| (!host && is_same_server) \
|
||||
|| (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host
|
||||
next
|
||||
end
|
||||
|
||||
next unless m = match.match(path.to_s)
|
||||
|
||||
rest = m[1]
|
||||
next unless !rest || rest.empty? || rest[0] == ?/
|
||||
|
||||
env[SCRIPT_NAME] = (script_name + location)
|
||||
env[PATH_INFO] = rest
|
||||
|
||||
return app.call(env)
|
||||
end
|
||||
|
||||
[404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
|
||||
|
||||
ensure
|
||||
env[PATH_INFO] = path
|
||||
env[SCRIPT_NAME] = script_name
|
||||
end
|
||||
|
||||
private
|
||||
def casecmp?(v1, v2)
|
||||
# if both nil, or they're the same string
|
||||
return true if v1 == v2
|
||||
|
||||
# if either are nil... (but they're not the same)
|
||||
return false if v1.nil?
|
||||
return false if v2.nil?
|
||||
|
||||
# otherwise check they're not case-insensitive the same
|
||||
v1.casecmp(v2).zero?
|
||||
end
|
||||
end
|
||||
end
|
||||
613
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/utils.rb
vendored
Normal file
613
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/utils.rb
vendored
Normal file
@ -0,0 +1,613 @@
|
||||
# -*- encoding: binary -*-
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'uri'
|
||||
require 'fileutils'
|
||||
require 'set'
|
||||
require 'tempfile'
|
||||
require 'time'
|
||||
|
||||
require_relative 'query_parser'
|
||||
|
||||
module Rack
|
||||
# Rack::Utils contains a grab-bag of useful methods for writing web
|
||||
# applications adopted from all kinds of Ruby libraries.
|
||||
|
||||
module Utils
|
||||
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
||||
|
||||
ParameterTypeError = QueryParser::ParameterTypeError
|
||||
InvalidParameterError = QueryParser::InvalidParameterError
|
||||
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
||||
COMMON_SEP = QueryParser::COMMON_SEP
|
||||
KeySpaceConstrainedParams = QueryParser::Params
|
||||
|
||||
class << self
|
||||
attr_accessor :default_query_parser
|
||||
end
|
||||
# The default number of bytes to allow parameter keys to take up.
|
||||
# This helps prevent a rogue client from flooding a Request.
|
||||
self.default_query_parser = QueryParser.make_default(65536, 100)
|
||||
|
||||
module_function
|
||||
|
||||
# URI escapes. (CGI style space to +)
|
||||
def escape(s)
|
||||
URI.encode_www_form_component(s)
|
||||
end
|
||||
|
||||
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
||||
# true URI escaping.
|
||||
def escape_path(s)
|
||||
::URI::DEFAULT_PARSER.escape s
|
||||
end
|
||||
|
||||
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
||||
# unescaping query parameters or form components.
|
||||
def unescape_path(s)
|
||||
::URI::DEFAULT_PARSER.unescape s
|
||||
end
|
||||
|
||||
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
||||
# target encoding of the string returned, and it defaults to UTF-8
|
||||
def unescape(s, encoding = Encoding::UTF_8)
|
||||
URI.decode_www_form_component(s, encoding)
|
||||
end
|
||||
|
||||
class << self
|
||||
attr_accessor :multipart_part_limit
|
||||
end
|
||||
|
||||
# The maximum number of parts a request can contain. Accepting too many part
|
||||
# can lead to the server running out of file handles.
|
||||
# Set to `0` for no limit.
|
||||
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || 128).to_i
|
||||
|
||||
def self.param_depth_limit
|
||||
default_query_parser.param_depth_limit
|
||||
end
|
||||
|
||||
def self.param_depth_limit=(v)
|
||||
self.default_query_parser = self.default_query_parser.new_depth_limit(v)
|
||||
end
|
||||
|
||||
def self.key_space_limit
|
||||
default_query_parser.key_space_limit
|
||||
end
|
||||
|
||||
def self.key_space_limit=(v)
|
||||
self.default_query_parser = self.default_query_parser.new_space_limit(v)
|
||||
end
|
||||
|
||||
if defined?(Process::CLOCK_MONOTONIC)
|
||||
def clock_time
|
||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
end
|
||||
else
|
||||
# :nocov:
|
||||
def clock_time
|
||||
Time.now.to_f
|
||||
end
|
||||
# :nocov:
|
||||
end
|
||||
|
||||
def parse_query(qs, d = nil, &unescaper)
|
||||
Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
|
||||
end
|
||||
|
||||
def parse_nested_query(qs, d = nil)
|
||||
Rack::Utils.default_query_parser.parse_nested_query(qs, d)
|
||||
end
|
||||
|
||||
def build_query(params)
|
||||
params.map { |k, v|
|
||||
if v.class == Array
|
||||
build_query(v.map { |x| [k, x] })
|
||||
else
|
||||
v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
|
||||
end
|
||||
}.join("&")
|
||||
end
|
||||
|
||||
def build_nested_query(value, prefix = nil)
|
||||
case value
|
||||
when Array
|
||||
value.map { |v|
|
||||
build_nested_query(v, "#{prefix}[]")
|
||||
}.join("&")
|
||||
when Hash
|
||||
value.map { |k, v|
|
||||
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
||||
}.delete_if(&:empty?).join('&')
|
||||
when nil
|
||||
prefix
|
||||
else
|
||||
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
||||
"#{prefix}=#{escape(value)}"
|
||||
end
|
||||
end
|
||||
|
||||
def q_values(q_value_header)
|
||||
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
||||
value, parameters = part.split(/\s*;\s*/, 2)
|
||||
quality = 1.0
|
||||
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
||||
quality = md[1].to_f
|
||||
end
|
||||
[value, quality]
|
||||
end
|
||||
end
|
||||
|
||||
# Return best accept value to use, based on the algorithm
|
||||
# in RFC 2616 Section 14. If there are multiple best
|
||||
# matches (same specificity and quality), the value returned
|
||||
# is arbitrary.
|
||||
def best_q_match(q_value_header, available_mimes)
|
||||
values = q_values(q_value_header)
|
||||
|
||||
matches = values.map do |req_mime, quality|
|
||||
match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
|
||||
next unless match
|
||||
[match, quality]
|
||||
end.compact.sort_by do |match, quality|
|
||||
(match.split('/', 2).count('*') * -10) + quality
|
||||
end.last
|
||||
matches && matches.first
|
||||
end
|
||||
|
||||
ESCAPE_HTML = {
|
||||
"&" => "&",
|
||||
"<" => "<",
|
||||
">" => ">",
|
||||
"'" => "'",
|
||||
'"' => """,
|
||||
"/" => "/"
|
||||
}
|
||||
|
||||
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
||||
|
||||
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
||||
def escape_html(string)
|
||||
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
||||
end
|
||||
|
||||
def select_best_encoding(available_encodings, accept_encoding)
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
|
||||
expanded_accept_encoding = []
|
||||
|
||||
accept_encoding.each do |m, q|
|
||||
preference = available_encodings.index(m) || available_encodings.size
|
||||
|
||||
if m == "*"
|
||||
(available_encodings - accept_encoding.map(&:first)).each do |m2|
|
||||
expanded_accept_encoding << [m2, q, preference]
|
||||
end
|
||||
else
|
||||
expanded_accept_encoding << [m, q, preference]
|
||||
end
|
||||
end
|
||||
|
||||
encoding_candidates = expanded_accept_encoding
|
||||
.sort_by { |_, q, p| [-q, p] }
|
||||
.map!(&:first)
|
||||
|
||||
unless encoding_candidates.include?("identity")
|
||||
encoding_candidates.push("identity")
|
||||
end
|
||||
|
||||
expanded_accept_encoding.each do |m, q|
|
||||
encoding_candidates.delete(m) if q == 0.0
|
||||
end
|
||||
|
||||
(encoding_candidates & available_encodings)[0]
|
||||
end
|
||||
|
||||
def parse_cookies(env)
|
||||
parse_cookies_header env[HTTP_COOKIE]
|
||||
end
|
||||
|
||||
def parse_cookies_header(header)
|
||||
# According to RFC 6265:
|
||||
# The syntax for cookie headers only supports semicolons
|
||||
# User Agent -> Server ==
|
||||
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
||||
return {} unless header
|
||||
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
||||
next if cookie.empty?
|
||||
key, value = cookie.split('=', 2)
|
||||
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
||||
end
|
||||
end
|
||||
|
||||
def add_cookie_to_header(header, key, value)
|
||||
case value
|
||||
when Hash
|
||||
domain = "; domain=#{value[:domain]}" if value[:domain]
|
||||
path = "; path=#{value[:path]}" if value[:path]
|
||||
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
||||
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
||||
secure = "; secure" if value[:secure]
|
||||
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
||||
same_site =
|
||||
case value[:same_site]
|
||||
when false, nil
|
||||
nil
|
||||
when :none, 'None', :None
|
||||
'; SameSite=None'
|
||||
when :lax, 'Lax', :Lax
|
||||
'; SameSite=Lax'
|
||||
when true, :strict, 'Strict', :Strict
|
||||
'; SameSite=Strict'
|
||||
else
|
||||
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
||||
end
|
||||
value = value[:value]
|
||||
end
|
||||
value = [value] unless Array === value
|
||||
|
||||
cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
|
||||
"#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
||||
|
||||
case header
|
||||
when nil, ''
|
||||
cookie
|
||||
when String
|
||||
[header, cookie].join("\n")
|
||||
when Array
|
||||
(header + [cookie]).join("\n")
|
||||
else
|
||||
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def set_cookie_header!(header, key, value)
|
||||
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
||||
nil
|
||||
end
|
||||
|
||||
def make_delete_cookie_header(header, key, value)
|
||||
case header
|
||||
when nil, ''
|
||||
cookies = []
|
||||
when String
|
||||
cookies = header.split("\n")
|
||||
when Array
|
||||
cookies = header
|
||||
end
|
||||
|
||||
key = escape(key)
|
||||
domain = value[:domain]
|
||||
path = value[:path]
|
||||
regexp = if domain
|
||||
if path
|
||||
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
||||
else
|
||||
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
||||
end
|
||||
elsif path
|
||||
/\A#{key}=.*path=#{path}(?:;|$)/
|
||||
else
|
||||
/\A#{key}=/
|
||||
end
|
||||
|
||||
cookies.reject! { |cookie| regexp.match? cookie }
|
||||
|
||||
cookies.join("\n")
|
||||
end
|
||||
|
||||
def delete_cookie_header!(header, key, value = {})
|
||||
header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
|
||||
nil
|
||||
end
|
||||
|
||||
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
||||
# strange method name.
|
||||
def add_remove_cookie_to_header(header, key, value = {})
|
||||
new_header = make_delete_cookie_header(header, key, value)
|
||||
|
||||
add_cookie_to_header(new_header, key,
|
||||
{ value: '', path: nil, domain: nil,
|
||||
max_age: '0',
|
||||
expires: Time.at(0) }.merge(value))
|
||||
|
||||
end
|
||||
|
||||
def rfc2822(time)
|
||||
time.rfc2822
|
||||
end
|
||||
|
||||
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
||||
# of '% %b %Y'.
|
||||
# It assumes that the time is in GMT to comply to the RFC 2109.
|
||||
#
|
||||
# NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
|
||||
# that I'm certain someone implemented only that option.
|
||||
# Do not use %a and %b from Time.strptime, it would use localized names for
|
||||
# weekday and month.
|
||||
#
|
||||
def rfc2109(time)
|
||||
wday = Time::RFC2822_DAY_NAME[time.wday]
|
||||
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
|
||||
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
||||
end
|
||||
|
||||
# Parses the "Range:" header, if present, into an array of Range objects.
|
||||
# Returns nil if the header is missing or syntactically invalid.
|
||||
# Returns an empty array if none of the ranges are satisfiable.
|
||||
def byte_ranges(env, size)
|
||||
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
||||
get_byte_ranges env['HTTP_RANGE'], size
|
||||
end
|
||||
|
||||
def get_byte_ranges(http_range, size)
|
||||
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
||||
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
||||
ranges = []
|
||||
$1.split(/,\s*/).each do |range_spec|
|
||||
return nil unless range_spec =~ /(\d*)-(\d*)/
|
||||
r0, r1 = $1, $2
|
||||
if r0.empty?
|
||||
return nil if r1.empty?
|
||||
# suffix-byte-range-spec, represents trailing suffix of file
|
||||
r0 = size - r1.to_i
|
||||
r0 = 0 if r0 < 0
|
||||
r1 = size - 1
|
||||
else
|
||||
r0 = r0.to_i
|
||||
if r1.empty?
|
||||
r1 = size - 1
|
||||
else
|
||||
r1 = r1.to_i
|
||||
return nil if r1 < r0 # backwards range is syntactically invalid
|
||||
r1 = size - 1 if r1 >= size
|
||||
end
|
||||
end
|
||||
ranges << (r0..r1) if r0 <= r1
|
||||
end
|
||||
ranges
|
||||
end
|
||||
|
||||
# Constant time string comparison.
|
||||
#
|
||||
# NOTE: the values compared should be of fixed length, such as strings
|
||||
# that have already been processed by HMAC. This should not be used
|
||||
# on variable length plaintext strings because it could leak length info
|
||||
# via timing attacks.
|
||||
def secure_compare(a, b)
|
||||
return false unless a.bytesize == b.bytesize
|
||||
|
||||
l = a.unpack("C*")
|
||||
|
||||
r, i = 0, -1
|
||||
b.each_byte { |v| r |= v ^ l[i += 1] }
|
||||
r == 0
|
||||
end
|
||||
|
||||
# Context allows the use of a compatible middleware at different points
|
||||
# in a request handling stack. A compatible middleware must define
|
||||
# #context which should take the arguments env and app. The first of which
|
||||
# would be the request environment. The second of which would be the rack
|
||||
# application that the request would be forwarded to.
|
||||
class Context
|
||||
attr_reader :for, :app
|
||||
|
||||
def initialize(app_f, app_r)
|
||||
raise 'running context does not respond to #context' unless app_f.respond_to? :context
|
||||
@for, @app = app_f, app_r
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@for.context(env, @app)
|
||||
end
|
||||
|
||||
def recontext(app)
|
||||
self.class.new(@for, app)
|
||||
end
|
||||
|
||||
def context(env, app = @app)
|
||||
recontext(app).call(env)
|
||||
end
|
||||
end
|
||||
|
||||
# A case-insensitive Hash that preserves the original case of a
|
||||
# header when set.
|
||||
#
|
||||
# @api private
|
||||
class HeaderHash < Hash # :nodoc:
|
||||
def self.[](headers)
|
||||
if headers.is_a?(HeaderHash) && !headers.frozen?
|
||||
return headers
|
||||
else
|
||||
return self.new(headers)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(hash = {})
|
||||
super()
|
||||
@names = {}
|
||||
hash.each { |k, v| self[k] = v }
|
||||
end
|
||||
|
||||
# on dup/clone, we need to duplicate @names hash
|
||||
def initialize_copy(other)
|
||||
super
|
||||
@names = other.names.dup
|
||||
end
|
||||
|
||||
# on clear, we need to clear @names hash
|
||||
def clear
|
||||
super
|
||||
@names.clear
|
||||
end
|
||||
|
||||
def each
|
||||
super do |k, v|
|
||||
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
||||
end
|
||||
end
|
||||
|
||||
def to_hash
|
||||
hash = {}
|
||||
each { |k, v| hash[k] = v }
|
||||
hash
|
||||
end
|
||||
|
||||
def [](k)
|
||||
super(k) || super(@names[k.downcase])
|
||||
end
|
||||
|
||||
def []=(k, v)
|
||||
canonical = k.downcase.freeze
|
||||
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
||||
@names[canonical] = k
|
||||
super k, v
|
||||
end
|
||||
|
||||
def delete(k)
|
||||
canonical = k.downcase
|
||||
result = super @names.delete(canonical)
|
||||
result
|
||||
end
|
||||
|
||||
def include?(k)
|
||||
super || @names.include?(k.downcase)
|
||||
end
|
||||
|
||||
alias_method :has_key?, :include?
|
||||
alias_method :member?, :include?
|
||||
alias_method :key?, :include?
|
||||
|
||||
def merge!(other)
|
||||
other.each { |k, v| self[k] = v }
|
||||
self
|
||||
end
|
||||
|
||||
def merge(other)
|
||||
hash = dup
|
||||
hash.merge! other
|
||||
end
|
||||
|
||||
def replace(other)
|
||||
clear
|
||||
other.each { |k, v| self[k] = v }
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
def names
|
||||
@names
|
||||
end
|
||||
end
|
||||
|
||||
# Every standard HTTP code mapped to the appropriate message.
|
||||
# Generated with:
|
||||
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
||||
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
||||
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
||||
HTTP_STATUS_CODES = {
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing',
|
||||
103 => 'Early Hints',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-Status',
|
||||
208 => 'Already Reported',
|
||||
226 => 'IM Used',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
306 => '(Unused)',
|
||||
307 => 'Temporary Redirect',
|
||||
308 => 'Permanent Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Payload Too Large',
|
||||
414 => 'URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
421 => 'Misdirected Request',
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
424 => 'Failed Dependency',
|
||||
425 => 'Too Early',
|
||||
426 => 'Upgrade Required',
|
||||
428 => 'Precondition Required',
|
||||
429 => 'Too Many Requests',
|
||||
431 => 'Request Header Fields Too Large',
|
||||
451 => 'Unavailable for Legal Reasons',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
506 => 'Variant Also Negotiates',
|
||||
507 => 'Insufficient Storage',
|
||||
508 => 'Loop Detected',
|
||||
509 => 'Bandwidth Limit Exceeded',
|
||||
510 => 'Not Extended',
|
||||
511 => 'Network Authentication Required'
|
||||
}
|
||||
|
||||
# Responses with HTTP status codes that should not have an entity body
|
||||
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
||||
|
||||
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
||||
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
||||
}.flatten]
|
||||
|
||||
def status_code(status)
|
||||
if status.is_a?(Symbol)
|
||||
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
||||
else
|
||||
status.to_i
|
||||
end
|
||||
end
|
||||
|
||||
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
||||
|
||||
def clean_path_info(path_info)
|
||||
parts = path_info.split PATH_SEPS
|
||||
|
||||
clean = []
|
||||
|
||||
parts.each do |part|
|
||||
next if part.empty? || part == '.'
|
||||
part == '..' ? clean.pop : clean << part
|
||||
end
|
||||
|
||||
clean_path = clean.join(::File::SEPARATOR)
|
||||
clean_path.prepend("/") if parts.empty? || parts.first.empty?
|
||||
clean_path
|
||||
end
|
||||
|
||||
NULL_BYTE = "\0"
|
||||
|
||||
def valid_path?(path)
|
||||
path.valid_encoding? && !path.include?(NULL_BYTE)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
29
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/version.rb
vendored
Normal file
29
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rack-2.2.3/lib/rack/version.rb
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Copyright (C) 2007-2019 Leah Neukirchen <http://leahneukirchen.org/infopage.html>
|
||||
#
|
||||
# Rack is freely distributable under the terms of an MIT-style license.
|
||||
# See MIT-LICENSE or https://opensource.org/licenses/MIT.
|
||||
|
||||
# The Rack main module, serving as a namespace for all core Rack
|
||||
# modules and classes.
|
||||
#
|
||||
# All modules meant for use in your application are <tt>autoload</tt>ed here,
|
||||
# so it should be enough just to <tt>require 'rack'</tt> in your code.
|
||||
|
||||
module Rack
|
||||
# The Rack protocol version number implemented.
|
||||
VERSION = [1, 3]
|
||||
|
||||
# Return the Rack protocol version as a dotted string.
|
||||
def self.version
|
||||
VERSION.join(".")
|
||||
end
|
||||
|
||||
RELEASE = "2.2.3"
|
||||
|
||||
# Return the Rack release as a dotted string.
|
||||
def self.release
|
||||
RELEASE
|
||||
end
|
||||
end
|
||||
726
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/config/default.yml
vendored
Normal file
726
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/config/default.yml
vendored
Normal file
@ -0,0 +1,726 @@
|
||||
# Common configuration.
|
||||
|
||||
inherit_mode:
|
||||
merge:
|
||||
- Exclude
|
||||
|
||||
AllCops:
|
||||
Exclude:
|
||||
- bin/*
|
||||
- db/schema.rb
|
||||
# What version of Rails is the inspected code using? If a value is specified
|
||||
# for TargetRailsVersion then it is used. Acceptable values are specificed
|
||||
# as a float (i.e. 5.1); the patch version of Rails should not be included.
|
||||
# If TargetRailsVersion is not set, RuboCop will parse the Gemfile.lock or
|
||||
# gems.locked file to find the version of Rails that has been bound to the
|
||||
# application. If neither of those files exist, RuboCop will use Rails 5.0
|
||||
# as the default.
|
||||
TargetRailsVersion: ~
|
||||
|
||||
Rails/ActionFilter:
|
||||
Description: 'Enforces consistent use of action filter methods.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.19'
|
||||
EnforcedStyle: action
|
||||
SupportedStyles:
|
||||
- action
|
||||
- filter
|
||||
Include:
|
||||
- app/controllers/**/*.rb
|
||||
|
||||
Rails/ActiveRecordAliases:
|
||||
Description: >-
|
||||
Avoid Active Record aliases:
|
||||
Use `update` instead of `update_attributes`.
|
||||
Use `update!` instead of `update_attributes!`.
|
||||
Enabled: true
|
||||
VersionAdded: '0.53'
|
||||
SafeAutoCorrect: false
|
||||
|
||||
Rails/ActiveRecordCallbacksOrder:
|
||||
Description: 'Order callback declarations in the order in which they will be executed.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#callbacks-order'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/ActiveRecordOverride:
|
||||
Description: >-
|
||||
Check for overriding Active Record methods instead of using
|
||||
callbacks.
|
||||
Enabled: true
|
||||
VersionAdded: '0.67'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/ActiveSupportAliases:
|
||||
Description: >-
|
||||
Avoid ActiveSupport aliases of standard ruby methods:
|
||||
`String#starts_with?`, `String#ends_with?`,
|
||||
`Array#append`, `Array#prepend`.
|
||||
Enabled: true
|
||||
VersionAdded: '0.48'
|
||||
|
||||
Rails/AfterCommitOverride:
|
||||
Description: >-
|
||||
This cop enforces that there is only one call to `after_commit`
|
||||
(and its aliases - `after_create_commit`, `after_update_commit`,
|
||||
and `after_destroy_commit`) with the same callback name per model.
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.8'
|
||||
|
||||
Rails/ApplicationController:
|
||||
Description: 'Check that controllers subclass ApplicationController.'
|
||||
Enabled: true
|
||||
SafeAutoCorrect: false
|
||||
VersionAdded: '2.4'
|
||||
VersionChanged: '2.5'
|
||||
|
||||
Rails/ApplicationJob:
|
||||
Description: 'Check that jobs subclass ApplicationJob.'
|
||||
Enabled: true
|
||||
SafeAutoCorrect: false
|
||||
VersionAdded: '0.49'
|
||||
VersionChanged: '2.5'
|
||||
|
||||
Rails/ApplicationMailer:
|
||||
Description: 'Check that mailers subclass ApplicationMailer.'
|
||||
Enabled: true
|
||||
SafeAutoCorrect: false
|
||||
VersionAdded: '2.4'
|
||||
VersionChanged: '2.5'
|
||||
|
||||
Rails/ApplicationRecord:
|
||||
Description: 'Check that models subclass ApplicationRecord.'
|
||||
Enabled: true
|
||||
SafeAutoCorrect: false
|
||||
VersionAdded: '0.49'
|
||||
VersionChanged: '2.5'
|
||||
|
||||
Rails/AssertNot:
|
||||
Description: 'Use `assert_not` instead of `assert !`.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.56'
|
||||
Include:
|
||||
- '**/test/**/*'
|
||||
|
||||
Rails/BelongsTo:
|
||||
Description: >-
|
||||
Use `optional: true` instead of `required: false` for
|
||||
`belongs_to` relations.
|
||||
Enabled: true
|
||||
VersionAdded: '0.62'
|
||||
|
||||
Rails/Blank:
|
||||
Description: 'Enforces use of `blank?`.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.48'
|
||||
VersionChanged: '0.67'
|
||||
# Convert usages of `nil? || empty?` to `blank?`
|
||||
NilOrEmpty: true
|
||||
# Convert usages of `!present?` to `blank?`
|
||||
NotPresent: true
|
||||
# Convert usages of `unless present?` to `if blank?`
|
||||
UnlessPresent: true
|
||||
|
||||
Rails/BulkChangeTable:
|
||||
Description: 'Check whether alter queries are combinable.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.57'
|
||||
Database: null
|
||||
SupportedDatabases:
|
||||
- mysql
|
||||
- postgresql
|
||||
Include:
|
||||
- db/migrate/*.rb
|
||||
|
||||
Rails/ContentTag:
|
||||
Description: 'Use `tag` instead of `content_tag`.'
|
||||
Reference:
|
||||
- 'https://github.com/rails/rails/issues/25195'
|
||||
- 'https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag'
|
||||
Enabled: true
|
||||
VersionAdded: '2.6'
|
||||
|
||||
Rails/CreateTableWithTimestamps:
|
||||
Description: >-
|
||||
Checks the migration for which timestamps are not included
|
||||
when creating a new table.
|
||||
Enabled: true
|
||||
VersionAdded: '0.52'
|
||||
Include:
|
||||
- db/migrate/*.rb
|
||||
|
||||
Rails/Date:
|
||||
Description: >-
|
||||
Checks the correct usage of date aware methods,
|
||||
such as Date.today, Date.current etc.
|
||||
Enabled: true
|
||||
VersionAdded: '0.30'
|
||||
VersionChanged: '0.33'
|
||||
# The value `strict` disallows usage of `Date.today`, `Date.current`,
|
||||
# `Date#to_time` etc.
|
||||
# The value `flexible` allows usage of `Date.current`, `Date.yesterday`, etc
|
||||
# (but not `Date.today`) which are overridden by ActiveSupport to handle current
|
||||
# time zone.
|
||||
EnforcedStyle: flexible
|
||||
SupportedStyles:
|
||||
- strict
|
||||
- flexible
|
||||
|
||||
Rails/DefaultScope:
|
||||
Description: 'Avoid use of `default_scope`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#avoid-default-scope'
|
||||
Enabled: false
|
||||
VersionAdded: '2.7'
|
||||
|
||||
Rails/Delegate:
|
||||
Description: 'Prefer delegate method for delegations.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.21'
|
||||
VersionChanged: '0.50'
|
||||
# When set to true, using the target object as a prefix of the
|
||||
# method name without using the `delegate` method will be a
|
||||
# violation. When set to false, this case is legal.
|
||||
EnforceForPrefixed: true
|
||||
|
||||
Rails/DelegateAllowBlank:
|
||||
Description: 'Do not use allow_blank as an option to delegate.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.44'
|
||||
|
||||
Rails/DynamicFindBy:
|
||||
Description: 'Use `find_by` instead of dynamic `find_by_*`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#find_by'
|
||||
Enabled: true
|
||||
VersionAdded: '0.44'
|
||||
VersionChanged: '2.6'
|
||||
# The `Whitelist` has been deprecated, Please use `AllowedMethods` instead.
|
||||
Whitelist:
|
||||
- find_by_sql
|
||||
AllowedMethods:
|
||||
- find_by_sql
|
||||
AllowedReceivers:
|
||||
- Gem::Specification
|
||||
|
||||
Rails/EnumHash:
|
||||
Description: 'Prefer hash syntax over array syntax when defining enums.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#enums'
|
||||
Enabled: true
|
||||
VersionAdded: '2.3'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/EnumUniqueness:
|
||||
Description: 'Avoid duplicate integers in hash-syntax `enum` declaration.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.46'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/EnvironmentComparison:
|
||||
Description: "Favor `Rails.env.production?` over `Rails.env == 'production'`."
|
||||
Enabled: true
|
||||
VersionAdded: '0.52'
|
||||
|
||||
Rails/Exit:
|
||||
Description: >-
|
||||
Favor `fail`, `break`, `return`, etc. over `exit` in
|
||||
application or library code outside of Rake files to avoid
|
||||
exits during unit testing or running in production.
|
||||
Enabled: true
|
||||
VersionAdded: '0.41'
|
||||
Include:
|
||||
- app/**/*.rb
|
||||
- config/**/*.rb
|
||||
- lib/**/*.rb
|
||||
Exclude:
|
||||
- lib/**/*.rake
|
||||
|
||||
Rails/FilePath:
|
||||
Description: 'Use `Rails.root.join` for file path joining.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.47'
|
||||
VersionChanged: '2.4'
|
||||
EnforcedStyle: slashes
|
||||
SupportedStyles:
|
||||
- slashes
|
||||
- arguments
|
||||
|
||||
Rails/FindBy:
|
||||
Description: 'Prefer find_by over where.first.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#find_by'
|
||||
Enabled: true
|
||||
VersionAdded: '0.30'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/FindById:
|
||||
Description: >-
|
||||
Favor the use of `find` over `where.take!`, `find_by!`, and `find_by_id!` when you
|
||||
need to retrieve a single record by primary key when you expect it to be found.
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#find'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
|
||||
Rails/FindEach:
|
||||
Description: 'Prefer all.find_each over all.find.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#find-each'
|
||||
Enabled: true
|
||||
VersionAdded: '0.30'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/HasAndBelongsToMany:
|
||||
Description: 'Prefer has_many :through to has_and_belongs_to_many.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#has-many-through'
|
||||
Enabled: true
|
||||
VersionAdded: '0.12'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/HasManyOrHasOneDependent:
|
||||
Description: 'Define the dependent option to the has_many and has_one associations.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#has_many-has_one-dependent-option'
|
||||
Enabled: true
|
||||
VersionAdded: '0.50'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/HelperInstanceVariable:
|
||||
Description: 'Do not use instance variables in helpers.'
|
||||
Enabled: true
|
||||
VersionAdded: '2.0'
|
||||
Include:
|
||||
- app/helpers/**/*.rb
|
||||
|
||||
Rails/HttpPositionalArguments:
|
||||
Description: 'Use keyword arguments instead of positional arguments in http method calls.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.44'
|
||||
Include:
|
||||
- 'spec/**/*'
|
||||
- 'test/**/*'
|
||||
|
||||
Rails/HttpStatus:
|
||||
Description: 'Enforces use of symbolic or numeric value to define HTTP status.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.54'
|
||||
EnforcedStyle: symbolic
|
||||
SupportedStyles:
|
||||
- numeric
|
||||
- symbolic
|
||||
|
||||
Rails/IgnoredSkipActionFilterOption:
|
||||
Description: 'Checks that `if` and `only` (or `except`) are not used together as options of `skip_*` action filter.'
|
||||
Reference: 'https://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html#method-i-_normalize_callback_options'
|
||||
Enabled: true
|
||||
VersionAdded: '0.63'
|
||||
Include:
|
||||
- app/controllers/**/*.rb
|
||||
|
||||
Rails/IndexBy:
|
||||
Description: 'Prefer `index_by` over `each_with_object`, `to_h`, or `map`.'
|
||||
Enabled: true
|
||||
VersionAdded: '2.5'
|
||||
VersionChanged: '2.8'
|
||||
|
||||
Rails/IndexWith:
|
||||
Description: 'Prefer `index_with` over `each_with_object`, `to_h`, or `map`.'
|
||||
Enabled: true
|
||||
VersionAdded: '2.5'
|
||||
VersionChanged: '2.8'
|
||||
|
||||
Rails/Inquiry:
|
||||
Description: "Prefer Ruby's comparison operators over Active Support's `Array#inquiry` and `String#inquiry`."
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#inquiry'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
|
||||
Rails/InverseOf:
|
||||
Description: 'Checks for associations where the inverse cannot be determined automatically.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.52'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/LexicallyScopedActionFilter:
|
||||
Description: "Checks that methods specified in the filter's `only` or `except` options are explicitly defined in the controller."
|
||||
StyleGuide: 'https://rails.rubystyle.guide#lexically-scoped-action-filter'
|
||||
Enabled: true
|
||||
Safe: false
|
||||
VersionAdded: '0.52'
|
||||
Include:
|
||||
- app/controllers/**/*.rb
|
||||
|
||||
Rails/LinkToBlank:
|
||||
Description: 'Checks that `link_to` with a `target: "_blank"` have a `rel: "noopener"` option passed to them.'
|
||||
Reference:
|
||||
- https://mathiasbynens.github.io/rel-noopener/
|
||||
- https://html.spec.whatwg.org/multipage/links.html#link-type-noopener
|
||||
- https://html.spec.whatwg.org/multipage/links.html#link-type-noreferrer
|
||||
Enabled: true
|
||||
VersionAdded: '0.62'
|
||||
|
||||
Rails/MailerName:
|
||||
Description: 'Mailer should end with `Mailer` suffix.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#mailer-name'
|
||||
Enabled: 'pending'
|
||||
SafeAutoCorrect: false
|
||||
VersionAdded: '2.7'
|
||||
Include:
|
||||
- app/mailers/**/*.rb
|
||||
|
||||
Rails/MatchRoute:
|
||||
Description: >-
|
||||
Don't use `match` to define any routes unless there is a need to map multiple request types
|
||||
among [:get, :post, :patch, :put, :delete] to a single action using the `:via` option.
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#no-match-routes'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
Include:
|
||||
- config/routes.rb
|
||||
- config/routes/**/*.rb
|
||||
|
||||
Rails/NegateInclude:
|
||||
Description: 'Prefer `collection.exclude?(obj)` over `!collection.include?(obj)`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#exclude'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
|
||||
Rails/NotNullColumn:
|
||||
Description: 'Do not add a NOT NULL column without a default value.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.43'
|
||||
Include:
|
||||
- db/migrate/*.rb
|
||||
|
||||
Rails/OrderById:
|
||||
Description: >-
|
||||
Do not use the `id` column for ordering.
|
||||
Use a timestamp column to order chronologically.
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#order-by-id'
|
||||
Enabled: false
|
||||
VersionAdded: '2.8'
|
||||
|
||||
Rails/Output:
|
||||
Description: 'Checks for calls to puts, print, etc.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.15'
|
||||
VersionChanged: '0.19'
|
||||
Include:
|
||||
- app/**/*.rb
|
||||
- config/**/*.rb
|
||||
- db/**/*.rb
|
||||
- lib/**/*.rb
|
||||
|
||||
Rails/OutputSafety:
|
||||
Description: 'The use of `html_safe` or `raw` may be a security risk.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.41'
|
||||
|
||||
Rails/Pick:
|
||||
Description: 'Prefer `pick` over `pluck(...).first`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#pick'
|
||||
Enabled: true
|
||||
Safe: false
|
||||
VersionAdded: '2.6'
|
||||
|
||||
Rails/Pluck:
|
||||
Description: 'Prefer `pluck` over `map { ... }`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#pluck'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
|
||||
Rails/PluckId:
|
||||
Description: 'Use `ids` instead of `pluck(:id)` or `pluck(primary_key)`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#ids'
|
||||
Enabled: false
|
||||
Safe: false
|
||||
VersionAdded: '2.7'
|
||||
|
||||
Rails/PluckInWhere:
|
||||
Description: 'Use `select` instead of `pluck` in `where` query methods.'
|
||||
Enabled: 'pending'
|
||||
Safe: false
|
||||
VersionAdded: '2.7'
|
||||
VersionChanged: '2.8'
|
||||
EnforcedStyle: conservative
|
||||
SupportedStyles:
|
||||
- conservative
|
||||
- aggressive
|
||||
|
||||
Rails/PluralizationGrammar:
|
||||
Description: 'Checks for incorrect grammar when using methods like `3.day.ago`.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.35'
|
||||
|
||||
Rails/Presence:
|
||||
Description: 'Checks code that can be written more easily using `Object#presence` defined by Active Support.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.52'
|
||||
|
||||
Rails/Present:
|
||||
Description: 'Enforces use of `present?`.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.48'
|
||||
VersionChanged: '0.67'
|
||||
# Convert usages of `!nil? && !empty?` to `present?`
|
||||
NotNilAndNotEmpty: true
|
||||
# Convert usages of `!blank?` to `present?`
|
||||
NotBlank: true
|
||||
# Convert usages of `unless blank?` to `if present?`
|
||||
UnlessBlank: true
|
||||
|
||||
Rails/RakeEnvironment:
|
||||
Description: 'Include `:environment` as a dependency for all Rake tasks.'
|
||||
Enabled: true
|
||||
Safe: false
|
||||
VersionAdded: '2.4'
|
||||
VersionChanged: '2.6'
|
||||
Include:
|
||||
- '**/Rakefile'
|
||||
- '**/*.rake'
|
||||
Exclude:
|
||||
- 'lib/capistrano/tasks/**/*.rake'
|
||||
|
||||
Rails/ReadWriteAttribute:
|
||||
Description: >-
|
||||
Checks for read_attribute(:attr) and
|
||||
write_attribute(:attr, val).
|
||||
StyleGuide: 'https://rails.rubystyle.guide#read-attribute'
|
||||
Enabled: true
|
||||
VersionAdded: '0.20'
|
||||
VersionChanged: '0.29'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/RedundantAllowNil:
|
||||
Description: >-
|
||||
Finds redundant use of `allow_nil` when `allow_blank` is set to
|
||||
certain values in model validations.
|
||||
Enabled: true
|
||||
VersionAdded: '0.67'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/RedundantForeignKey:
|
||||
Description: 'Checks for associations where the `:foreign_key` option is redundant.'
|
||||
Enabled: true
|
||||
VersionAdded: '2.6'
|
||||
|
||||
Rails/RedundantReceiverInWithOptions:
|
||||
Description: 'Checks for redundant receiver in `with_options`.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.52'
|
||||
|
||||
Rails/ReflectionClassName:
|
||||
Description: 'Use a string for `class_name` option value in the definition of a reflection.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.64'
|
||||
|
||||
Rails/RefuteMethods:
|
||||
Description: 'Use `assert_not` methods instead of `refute` methods.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.56'
|
||||
EnforcedStyle: assert_not
|
||||
SupportedStyles:
|
||||
- assert_not
|
||||
- refute
|
||||
Include:
|
||||
- '**/test/**/*'
|
||||
|
||||
Rails/RelativeDateConstant:
|
||||
Description: 'Do not assign relative date to constants.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.48'
|
||||
VersionChanged: '0.59'
|
||||
AutoCorrect: false
|
||||
|
||||
Rails/RenderInline:
|
||||
Description: 'Prefer using a template over inline rendering.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#inline-rendering'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
|
||||
Rails/RenderPlainText:
|
||||
Description: 'Prefer `render plain:` over `render text:`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#plain-text-rendering'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
# Convert only when `content_type` is explicitly set to `text/plain`.
|
||||
ContentTypeCompatibility: true
|
||||
|
||||
Rails/RequestReferer:
|
||||
Description: 'Use consistent syntax for request.referer.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.41'
|
||||
EnforcedStyle: referer
|
||||
SupportedStyles:
|
||||
- referer
|
||||
- referrer
|
||||
|
||||
Rails/ReversibleMigration:
|
||||
Description: 'Checks whether the change method of the migration file is reversible.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#reversible-migration'
|
||||
Reference: 'https://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html'
|
||||
Enabled: true
|
||||
VersionAdded: '0.47'
|
||||
Include:
|
||||
- db/migrate/*.rb
|
||||
|
||||
Rails/SafeNavigation:
|
||||
Description: "Use Ruby's safe navigation operator (`&.`) instead of `try!`."
|
||||
Enabled: true
|
||||
VersionAdded: '0.43'
|
||||
# This will convert usages of `try` to use safe navigation as well as `try!`.
|
||||
# `try` and `try!` work slightly differently. `try!` and safe navigation will
|
||||
# both raise a `NoMethodError` if the receiver of the method call does not
|
||||
# implement the intended method. `try` will not raise an exception for this.
|
||||
ConvertTry: false
|
||||
|
||||
Rails/SafeNavigationWithBlank:
|
||||
Description: 'Avoid `foo&.blank?` in conditionals.'
|
||||
Enabled: true
|
||||
VersionAdded: '2.4'
|
||||
# While the safe navigation operator is generally a good idea, when
|
||||
# checking `foo&.blank?` in a conditional, `foo` being `nil` will actually
|
||||
# do the opposite of what the author intends.
|
||||
#
|
||||
# foo&.blank? #=> nil
|
||||
# foo.blank? #=> true
|
||||
SafeAutoCorrect: false
|
||||
|
||||
Rails/SaveBang:
|
||||
Description: 'Identifies possible cases where Active Record save! or related should be used.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#save-bang'
|
||||
Enabled: false
|
||||
VersionAdded: '0.42'
|
||||
VersionChanged: '0.59'
|
||||
AllowImplicitReturn: true
|
||||
AllowedReceivers: []
|
||||
SafeAutoCorrect: false
|
||||
|
||||
Rails/ScopeArgs:
|
||||
Description: 'Checks the arguments of ActiveRecord scopes.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.19'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/ShortI18n:
|
||||
Description: 'Use the short form of the I18n methods: `t` instead of `translate` and `l` instead of `localize`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#short-i18n'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.7'
|
||||
EnforcedStyle: conservative
|
||||
SupportedStyles:
|
||||
- conservative
|
||||
- aggressive
|
||||
|
||||
Rails/SkipsModelValidations:
|
||||
Description: >-
|
||||
Use methods that skips model validations with caution.
|
||||
See reference for more information.
|
||||
Reference: 'https://guides.rubyonrails.org/active_record_validations.html#skipping-validations'
|
||||
Enabled: true
|
||||
VersionAdded: '0.47'
|
||||
VersionChanged: '2.7'
|
||||
ForbiddenMethods:
|
||||
- decrement!
|
||||
- decrement_counter
|
||||
- increment!
|
||||
- increment_counter
|
||||
- insert
|
||||
- insert!
|
||||
- insert_all
|
||||
- insert_all!
|
||||
- toggle!
|
||||
- touch
|
||||
- touch_all
|
||||
- update_all
|
||||
- update_attribute
|
||||
- update_column
|
||||
- update_columns
|
||||
- update_counters
|
||||
- upsert
|
||||
- upsert_all
|
||||
AllowedMethods: []
|
||||
|
||||
Rails/SquishedSQLHeredocs:
|
||||
Description: 'Checks SQL heredocs to use `.squish`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#squished-heredocs'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.8'
|
||||
|
||||
Rails/TimeZone:
|
||||
Description: 'Checks the correct usage of time zone aware methods.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide#time'
|
||||
Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
|
||||
Enabled: true
|
||||
Safe: false
|
||||
VersionAdded: '0.30'
|
||||
VersionChanged: '0.68'
|
||||
# The value `strict` means that `Time` should be used with `zone`.
|
||||
# The value `flexible` allows usage of `in_time_zone` instead of `zone`.
|
||||
EnforcedStyle: flexible
|
||||
SupportedStyles:
|
||||
- strict
|
||||
- flexible
|
||||
|
||||
Rails/UniqBeforePluck:
|
||||
Description: 'Prefer the use of uniq or distinct before pluck.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.40'
|
||||
VersionChanged: '2.8'
|
||||
EnforcedStyle: conservative
|
||||
SupportedStyles:
|
||||
- conservative
|
||||
- aggressive
|
||||
SafeAutoCorrect: false
|
||||
AutoCorrect: false
|
||||
|
||||
Rails/UniqueValidationWithoutIndex:
|
||||
Description: 'Uniqueness validation should be with a unique index.'
|
||||
Enabled: true
|
||||
VersionAdded: '2.5'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/UnknownEnv:
|
||||
Description: 'Use correct environment name.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.51'
|
||||
Environments:
|
||||
- development
|
||||
- test
|
||||
- production
|
||||
|
||||
Rails/Validation:
|
||||
Description: 'Use validates :attribute, hash of validations.'
|
||||
Enabled: true
|
||||
VersionAdded: '0.9'
|
||||
VersionChanged: '0.41'
|
||||
Include:
|
||||
- app/models/**/*.rb
|
||||
|
||||
Rails/WhereExists:
|
||||
Description: 'Prefer `exists?(...)` over `where(...).exists?`.'
|
||||
Enabled: 'pending'
|
||||
EnforcedStyle: exists
|
||||
SupportedStyles:
|
||||
- exists
|
||||
- where
|
||||
VersionAdded: '2.7'
|
||||
VersionChanged: '2.8'
|
||||
|
||||
Rails/WhereNot:
|
||||
Description: 'Use `where.not(...)` instead of manually constructing negated SQL in `where`.'
|
||||
StyleGuide: 'https://rails.rubystyle.guide/#where-not'
|
||||
Enabled: 'pending'
|
||||
VersionAdded: '2.8'
|
||||
|
||||
# Accept `redirect_to(...) and return` and similar cases.
|
||||
Style/AndOr:
|
||||
EnforcedStyle: conditionals
|
||||
15
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop-rails.rb
vendored
Normal file
15
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop-rails.rb
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rubocop'
|
||||
require 'rack/utils'
|
||||
require 'active_support/inflector'
|
||||
|
||||
require_relative 'rubocop/rails'
|
||||
require_relative 'rubocop/rails/version'
|
||||
require_relative 'rubocop/rails/inject'
|
||||
require_relative 'rubocop/rails/schema_loader'
|
||||
require_relative 'rubocop/rails/schema_loader/schema'
|
||||
|
||||
RuboCop::Rails::Inject.defaults!
|
||||
|
||||
require_relative 'rubocop/cop/rails_cops'
|
||||
@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
# A mixin to extend cops for Active Record features
|
||||
module ActiveRecordHelper
|
||||
extend NodePattern::Macros
|
||||
|
||||
WHERE_METHODS = %i[where rewhere].freeze
|
||||
|
||||
def_node_search :find_set_table_name, <<~PATTERN
|
||||
(send self :table_name= {str sym})
|
||||
PATTERN
|
||||
|
||||
def_node_search :find_belongs_to, <<~PATTERN
|
||||
(send nil? :belongs_to {str sym} ...)
|
||||
PATTERN
|
||||
|
||||
def external_dependency_checksum
|
||||
return @external_dependency_checksum if defined?(@external_dependency_checksum)
|
||||
|
||||
schema_path = RuboCop::Rails::SchemaLoader.db_schema_path
|
||||
return nil if schema_path.nil?
|
||||
|
||||
schema_code = File.read(schema_path)
|
||||
|
||||
@external_dependency_checksum ||= Digest::SHA1.hexdigest(schema_code)
|
||||
end
|
||||
|
||||
def schema
|
||||
RuboCop::Rails::SchemaLoader.load(target_ruby_version)
|
||||
end
|
||||
|
||||
def table_name(class_node)
|
||||
table_name = find_set_table_name(class_node).to_a.last&.first_argument
|
||||
return table_name.value.to_s if table_name
|
||||
|
||||
namespaces = class_node.each_ancestor(:class, :module)
|
||||
[class_node, *namespaces]
|
||||
.reverse
|
||||
.map { |klass| klass.identifier.children[1] }.join('_')
|
||||
.tableize
|
||||
end
|
||||
|
||||
# Resolve relation into column name.
|
||||
# It just returns column_name if the column exists.
|
||||
# Or it tries to resolve column_name as a relation.
|
||||
# It returns `nil` if it can't resolve.
|
||||
#
|
||||
# @param name [String]
|
||||
# @param class_node [RuboCop::AST::Node]
|
||||
# @param table [RuboCop::Rails::SchemaLoader::Table]
|
||||
# @return [String, nil]
|
||||
def resolve_relation_into_column(name:, class_node:, table:)
|
||||
return unless table
|
||||
return name if table.with_column?(name: name)
|
||||
|
||||
find_belongs_to(class_node) do |belongs_to|
|
||||
next unless belongs_to.first_argument.value.to_s == name
|
||||
|
||||
fk = foreign_key_of(belongs_to) || "#{name}_id"
|
||||
return fk if table.with_column?(name: fk)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def foreign_key_of(belongs_to)
|
||||
options = belongs_to.last_argument
|
||||
return unless options.hash_type?
|
||||
|
||||
options.each_pair.find do |pair|
|
||||
next unless pair.key.sym_type? && pair.key.value == :foreign_key
|
||||
next unless pair.value.sym_type? || pair.value.str_type?
|
||||
|
||||
break pair.value.value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def in_where?(node)
|
||||
send_node = node.each_ancestor(:send).first
|
||||
send_node && WHERE_METHODS.include?(send_node.method_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,178 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
# Common functionality for Rails/IndexBy and Rails/IndexWith
|
||||
module IndexMethod # rubocop:disable Metrics/ModuleLength
|
||||
def on_block(node)
|
||||
on_bad_each_with_object(node) do |*match|
|
||||
handle_possible_offense(node, match, 'each_with_object')
|
||||
end
|
||||
|
||||
return if target_ruby_version < 2.6
|
||||
|
||||
on_bad_to_h(node) do |*match|
|
||||
handle_possible_offense(node, match, 'to_h { ... }')
|
||||
end
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
on_bad_map_to_h(node) do |*match|
|
||||
handle_possible_offense(node, match, 'map { ... }.to_h')
|
||||
end
|
||||
|
||||
on_bad_hash_brackets_map(node) do |*match|
|
||||
handle_possible_offense(node, match, 'Hash[map { ... }]')
|
||||
end
|
||||
end
|
||||
|
||||
def on_csend(node)
|
||||
on_bad_map_to_h(node) do |*match|
|
||||
handle_possible_offense(node, match, 'map { ... }.to_h')
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
correction = prepare_correction(node)
|
||||
execute_correction(corrector, node, correction)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @abstract Implemented with `def_node_matcher`
|
||||
def on_bad_each_with_object(_node)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# @abstract Implemented with `def_node_matcher`
|
||||
def on_bad_to_h(_node)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# @abstract Implemented with `def_node_matcher`
|
||||
def on_bad_map_to_h(_node)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# @abstract Implemented with `def_node_matcher`
|
||||
def on_bad_hash_brackets_map(_node)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def handle_possible_offense(node, match, match_desc)
|
||||
captures = extract_captures(match)
|
||||
|
||||
return if captures.noop_transformation?
|
||||
|
||||
add_offense(
|
||||
node,
|
||||
message: "Prefer `#{new_method_name}` over `#{match_desc}`."
|
||||
)
|
||||
end
|
||||
|
||||
def extract_captures(match)
|
||||
argname, body_expr = *match
|
||||
Captures.new(argname, body_expr)
|
||||
end
|
||||
|
||||
def new_method_name
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def prepare_correction(node)
|
||||
if (match = on_bad_each_with_object(node))
|
||||
Autocorrection.from_each_with_object(node, match)
|
||||
elsif (match = on_bad_to_h(node))
|
||||
Autocorrection.from_to_h(node, match)
|
||||
elsif (match = on_bad_map_to_h(node))
|
||||
Autocorrection.from_map_to_h(node, match)
|
||||
elsif (match = on_bad_hash_brackets_map(node))
|
||||
Autocorrection.from_hash_brackets_map(node, match)
|
||||
else
|
||||
raise 'unreachable'
|
||||
end
|
||||
end
|
||||
|
||||
def execute_correction(corrector, node, correction)
|
||||
correction.strip_prefix_and_suffix(node, corrector)
|
||||
correction.set_new_method_name(new_method_name, corrector)
|
||||
|
||||
captures = extract_captures(correction.match)
|
||||
correction.set_new_arg_name(captures.transformed_argname, corrector)
|
||||
correction.set_new_body_expression(
|
||||
captures.transforming_body_expr,
|
||||
corrector
|
||||
)
|
||||
end
|
||||
|
||||
# Internal helper class to hold match data
|
||||
Captures = Struct.new(
|
||||
:transformed_argname,
|
||||
:transforming_body_expr
|
||||
) do
|
||||
def noop_transformation?
|
||||
transforming_body_expr.lvar_type? &&
|
||||
transforming_body_expr.children == [transformed_argname]
|
||||
end
|
||||
end
|
||||
|
||||
# Internal helper class to hold autocorrect data
|
||||
Autocorrection = Struct.new(:match, :block_node, :leading, :trailing) do # rubocop:disable Metrics/BlockLength
|
||||
def self.from_each_with_object(node, match)
|
||||
new(match, node, 0, 0)
|
||||
end
|
||||
|
||||
def self.from_to_h(node, match)
|
||||
new(match, node, 0, 0)
|
||||
end
|
||||
|
||||
def self.from_map_to_h(node, match)
|
||||
strip_trailing_chars = 0
|
||||
|
||||
unless node.parent&.block_type?
|
||||
map_range = node.children.first.source_range
|
||||
node_range = node.source_range
|
||||
strip_trailing_chars = node_range.end_pos - map_range.end_pos
|
||||
end
|
||||
|
||||
new(match, node.children.first, 0, strip_trailing_chars)
|
||||
end
|
||||
|
||||
def self.from_hash_brackets_map(node, match)
|
||||
new(match, node.children.last, 'Hash['.length, ']'.length)
|
||||
end
|
||||
|
||||
def strip_prefix_and_suffix(node, corrector)
|
||||
expression = node.loc.expression
|
||||
corrector.remove_leading(expression, leading)
|
||||
corrector.remove_trailing(expression, trailing)
|
||||
end
|
||||
|
||||
def set_new_method_name(new_method_name, corrector)
|
||||
range = block_node.send_node.loc.selector
|
||||
if (send_end = block_node.send_node.loc.end)
|
||||
# If there are arguments (only true in the `each_with_object` case)
|
||||
range = range.begin.join(send_end)
|
||||
end
|
||||
corrector.replace(range, new_method_name)
|
||||
end
|
||||
|
||||
def set_new_arg_name(transformed_argname, corrector)
|
||||
corrector.replace(
|
||||
block_node.arguments.loc.expression,
|
||||
"|#{transformed_argname}|"
|
||||
)
|
||||
end
|
||||
|
||||
def set_new_body_expression(transforming_body_expr, corrector)
|
||||
corrector.replace(
|
||||
block_node.body.loc.expression,
|
||||
transforming_body_expr.loc.expression.source
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
# Common functionality for checking target rails version.
|
||||
module TargetRailsVersion
|
||||
def minimum_target_rails_version(version)
|
||||
@minimum_target_rails_version = version
|
||||
end
|
||||
|
||||
def support_target_rails_version?(version)
|
||||
@minimum_target_rails_version <= version
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,111 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop enforces the consistent use of action filter methods.
|
||||
#
|
||||
# The cop is configurable and can enforce the use of the older
|
||||
# something_filter methods or the newer something_action methods.
|
||||
#
|
||||
# @example EnforcedStyle: action (default)
|
||||
# # bad
|
||||
# after_filter :do_stuff
|
||||
# append_around_filter :do_stuff
|
||||
# skip_after_filter :do_stuff
|
||||
#
|
||||
# # good
|
||||
# after_action :do_stuff
|
||||
# append_around_action :do_stuff
|
||||
# skip_after_action :do_stuff
|
||||
#
|
||||
# @example EnforcedStyle: filter
|
||||
# # bad
|
||||
# after_action :do_stuff
|
||||
# append_around_action :do_stuff
|
||||
# skip_after_action :do_stuff
|
||||
#
|
||||
# # good
|
||||
# after_filter :do_stuff
|
||||
# append_around_filter :do_stuff
|
||||
# skip_after_filter :do_stuff
|
||||
class ActionFilter < Cop
|
||||
include ConfigurableEnforcedStyle
|
||||
|
||||
MSG = 'Prefer `%<prefer>s` over `%<current>s`.'
|
||||
|
||||
FILTER_METHODS = %i[
|
||||
after_filter
|
||||
append_after_filter
|
||||
append_around_filter
|
||||
append_before_filter
|
||||
around_filter
|
||||
before_filter
|
||||
prepend_after_filter
|
||||
prepend_around_filter
|
||||
prepend_before_filter
|
||||
skip_after_filter
|
||||
skip_around_filter
|
||||
skip_before_filter
|
||||
skip_filter
|
||||
].freeze
|
||||
|
||||
ACTION_METHODS = %i[
|
||||
after_action
|
||||
append_after_action
|
||||
append_around_action
|
||||
append_before_action
|
||||
around_action
|
||||
before_action
|
||||
prepend_after_action
|
||||
prepend_around_action
|
||||
prepend_before_action
|
||||
skip_after_action
|
||||
skip_around_action
|
||||
skip_before_action
|
||||
skip_action_callback
|
||||
].freeze
|
||||
|
||||
def on_block(node)
|
||||
check_method_node(node.send_node)
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
check_method_node(node) unless node.receiver
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.loc.selector,
|
||||
preferred_method(node.loc.selector.source).to_s)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_method_node(node)
|
||||
return unless bad_methods.include?(node.method_name)
|
||||
|
||||
add_offense(node, location: :selector)
|
||||
end
|
||||
|
||||
def message(node)
|
||||
format(MSG, prefer: preferred_method(node.method_name),
|
||||
current: node.method_name)
|
||||
end
|
||||
|
||||
def bad_methods
|
||||
style == :action ? FILTER_METHODS : ACTION_METHODS
|
||||
end
|
||||
|
||||
def good_methods
|
||||
style == :action ? ACTION_METHODS : FILTER_METHODS
|
||||
end
|
||||
|
||||
def preferred_method(method)
|
||||
good_methods[bad_methods.index(method.to_sym)]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# Checks that ActiveRecord aliases are not used. The direct method names
|
||||
# are more clear and easier to read.
|
||||
#
|
||||
# @example
|
||||
# #bad
|
||||
# Book.update_attributes!(author: 'Alice')
|
||||
#
|
||||
# #good
|
||||
# Book.update!(author: 'Alice')
|
||||
class ActiveRecordAliases < Cop
|
||||
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
||||
|
||||
ALIASES = {
|
||||
update_attributes: :update,
|
||||
update_attributes!: :update!
|
||||
}.freeze
|
||||
|
||||
def on_send(node)
|
||||
ALIASES.each do |bad, good|
|
||||
next unless node.method?(bad)
|
||||
|
||||
add_offense(node,
|
||||
message: format(MSG, prefer: good, current: bad),
|
||||
location: :selector,
|
||||
severity: :warning)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
alias on_csend on_send
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(
|
||||
node.loc.selector,
|
||||
ALIASES[node.method_name].to_s
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,143 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that Active Record callbacks are declared
|
||||
# in the order in which they will be executed.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# class Person < ApplicationRecord
|
||||
# after_commit :after_commit_callback
|
||||
# before_validation :before_validation_callback
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# class Person < ApplicationRecord
|
||||
# before_validation :before_validation_callback
|
||||
# after_commit :after_commit_callback
|
||||
# end
|
||||
#
|
||||
class ActiveRecordCallbacksOrder < Cop
|
||||
MSG = '`%<current>s` is supposed to appear before `%<previous>s`.'
|
||||
|
||||
CALLBACKS_IN_ORDER = %i[
|
||||
after_initialize
|
||||
before_validation
|
||||
after_validation
|
||||
before_save
|
||||
around_save
|
||||
before_create
|
||||
around_create
|
||||
after_create
|
||||
before_update
|
||||
around_update
|
||||
after_update
|
||||
before_destroy
|
||||
around_destroy
|
||||
after_destroy
|
||||
after_save
|
||||
after_commit
|
||||
after_rollback
|
||||
after_find
|
||||
after_touch
|
||||
].freeze
|
||||
|
||||
CALLBACKS_ORDER_MAP = CALLBACKS_IN_ORDER.each_with_index.to_h.freeze
|
||||
|
||||
def on_class(class_node)
|
||||
previous_index = -1
|
||||
previous_callback = nil
|
||||
|
||||
defined_callbacks(class_node).each do |node|
|
||||
callback = node.method_name
|
||||
index = CALLBACKS_ORDER_MAP[callback]
|
||||
|
||||
if index < previous_index
|
||||
message = format(MSG, current: callback,
|
||||
previous: previous_callback)
|
||||
add_offense(node, message: message)
|
||||
end
|
||||
previous_index = index
|
||||
previous_callback = callback
|
||||
end
|
||||
end
|
||||
|
||||
# Autocorrect by swapping between two nodes autocorrecting them
|
||||
def autocorrect(node)
|
||||
previous = left_siblings_of(node).reverse_each.find do |sibling|
|
||||
callback?(sibling)
|
||||
end
|
||||
|
||||
current_range = source_range_with_comment(node)
|
||||
previous_range = source_range_with_comment(previous)
|
||||
|
||||
lambda do |corrector|
|
||||
corrector.insert_before(previous_range, current_range.source)
|
||||
corrector.remove(current_range)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def defined_callbacks(class_node)
|
||||
class_def = class_node.body
|
||||
|
||||
if class_def
|
||||
class_def.each_child_node.select { |c| callback?(c) }
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def callback?(node)
|
||||
node.send_type? && CALLBACKS_ORDER_MAP.key?(node.method_name)
|
||||
end
|
||||
|
||||
def left_siblings_of(node)
|
||||
siblings_of(node)[0, node.sibling_index]
|
||||
end
|
||||
|
||||
def siblings_of(node)
|
||||
node.parent.children
|
||||
end
|
||||
|
||||
def source_range_with_comment(node)
|
||||
begin_pos = begin_pos_with_comment(node)
|
||||
end_pos = end_position_for(node)
|
||||
|
||||
Parser::Source::Range.new(buffer, begin_pos, end_pos)
|
||||
end
|
||||
|
||||
def end_position_for(node)
|
||||
end_line = buffer.line_for_position(node.loc.expression.end_pos)
|
||||
buffer.line_range(end_line).end_pos
|
||||
end
|
||||
|
||||
def begin_pos_with_comment(node)
|
||||
annotation_line = node.first_line - 1
|
||||
first_comment = nil
|
||||
|
||||
processed_source.comments_before_line(annotation_line)
|
||||
.reverse_each do |comment|
|
||||
if comment.location.line == annotation_line
|
||||
first_comment = comment
|
||||
annotation_line -= 1
|
||||
end
|
||||
end
|
||||
|
||||
start_line_position(first_comment || node)
|
||||
end
|
||||
|
||||
def start_line_position(node)
|
||||
buffer.line_range(node.loc.line).begin_pos - 1
|
||||
end
|
||||
|
||||
def buffer
|
||||
processed_source.buffer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# Checks for overriding built-in Active Record methods instead of using
|
||||
# callbacks.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# class Book < ApplicationRecord
|
||||
# def save
|
||||
# self.title = title.upcase!
|
||||
# super
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# class Book < ApplicationRecord
|
||||
# before_save :upcase_title
|
||||
#
|
||||
# def upcase_title
|
||||
# self.title = title.upcase!
|
||||
# end
|
||||
# end
|
||||
#
|
||||
class ActiveRecordOverride < Cop
|
||||
MSG =
|
||||
'Use %<prefer>s callbacks instead of overriding the Active Record ' \
|
||||
'method `%<bad>s`.'
|
||||
BAD_METHODS = %i[create destroy save update].freeze
|
||||
ACTIVE_RECORD_CLASSES = %w[ApplicationRecord ActiveModel::Base
|
||||
ActiveRecord::Base].freeze
|
||||
|
||||
def on_def(node)
|
||||
return unless BAD_METHODS.include?(node.method_name)
|
||||
|
||||
parent_class_name = find_parent_class_name(node)
|
||||
return unless active_model?(parent_class_name)
|
||||
|
||||
return unless node.descendants.any?(&:zsuper_type?)
|
||||
|
||||
add_offense(node, message: message(node.method_name))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def active_model?(parent_class_name)
|
||||
ACTIVE_RECORD_CLASSES.include?(parent_class_name)
|
||||
end
|
||||
|
||||
def callback_names(method_name)
|
||||
names = %w[before_ around_ after_].map do |prefix|
|
||||
"`#{prefix}#{method_name}`"
|
||||
end
|
||||
|
||||
names[-1] = "or #{names.last}"
|
||||
|
||||
names.join(', ')
|
||||
end
|
||||
|
||||
def message(method_name)
|
||||
format(MSG, prefer: callback_names(method_name), bad: method_name)
|
||||
end
|
||||
|
||||
def find_parent_class_name(node)
|
||||
return nil unless node
|
||||
|
||||
if node.class_type?
|
||||
parent_class_name = node.node_parts[1]
|
||||
|
||||
return nil if parent_class_name.nil?
|
||||
|
||||
return parent_class_name.source
|
||||
end
|
||||
|
||||
find_parent_class_name(node.parent)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that ActiveSupport aliases to core ruby methods
|
||||
# are not used.
|
||||
#
|
||||
# @example
|
||||
# # good
|
||||
# 'some_string'.start_with?('prefix')
|
||||
# 'some_string'.end_with?('suffix')
|
||||
# [1, 2, 'a'] << 'b'
|
||||
# [1, 2, 'a'].unshift('b')
|
||||
#
|
||||
# # bad
|
||||
# 'some_string'.starts_with?('prefix')
|
||||
# 'some_string'.ends_with?('suffix')
|
||||
# [1, 2, 'a'].append('b')
|
||||
# [1, 2, 'a'].prepend('b')
|
||||
#
|
||||
class ActiveSupportAliases < Cop
|
||||
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
||||
|
||||
ALIASES = {
|
||||
starts_with?: {
|
||||
original: :start_with?, matcher: '(send str :starts_with? _)'
|
||||
},
|
||||
ends_with?: {
|
||||
original: :end_with?, matcher: '(send str :ends_with? _)'
|
||||
},
|
||||
append: { original: :<<, matcher: '(send array :append _)' },
|
||||
prepend: { original: :unshift, matcher: '(send array :prepend _)' }
|
||||
}.freeze
|
||||
|
||||
ALIASES.each do |aliased_method, options|
|
||||
def_node_matcher aliased_method, options[:matcher]
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
ALIASES.each_key do |aliased_method|
|
||||
register_offense(node, aliased_method) if
|
||||
public_send(aliased_method, node)
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
return false if append(node)
|
||||
|
||||
lambda do |corrector|
|
||||
method_name = node.loc.selector.source
|
||||
replacement = ALIASES[method_name.to_sym][:original]
|
||||
corrector.replace(node.loc.selector, replacement.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def register_offense(node, method_name)
|
||||
add_offense(
|
||||
node,
|
||||
message: format(MSG, prefer: ALIASES[method_name][:original],
|
||||
current: method_name)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,91 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop enforces that there is only one call to `after_commit`
|
||||
# (and its aliases - `after_create_commit`, `after_update_commit`,
|
||||
# and `after_destroy_commit`) with the same callback name per model.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# # This won't be triggered.
|
||||
# after_create_commit :log_action
|
||||
#
|
||||
# # This will override the callback added by
|
||||
# # after_create_commit.
|
||||
# after_update_commit :log_action
|
||||
#
|
||||
# # bad
|
||||
# # This won't be triggered.
|
||||
# after_commit :log_action, on: :create
|
||||
# # This won't be triggered.
|
||||
# after_update_commit :log_action
|
||||
# # This will override both previous callbacks.
|
||||
# after_commit :log_action, on: :destroy
|
||||
#
|
||||
# # good
|
||||
# after_save_commit :log_action
|
||||
#
|
||||
# # good
|
||||
# after_create_commit :log_create_action
|
||||
# after_update_commit :log_update_action
|
||||
#
|
||||
class AfterCommitOverride < Cop
|
||||
MSG = 'There can only be one `after_*_commit :%<name>s` hook defined for a model.'
|
||||
|
||||
AFTER_COMMIT_CALLBACKS = %i[
|
||||
after_commit
|
||||
after_create_commit
|
||||
after_update_commit
|
||||
after_save_commit
|
||||
after_destroy_commit
|
||||
].freeze
|
||||
|
||||
def on_class(class_node)
|
||||
seen_callback_names = {}
|
||||
|
||||
each_after_commit_callback(class_node) do |node|
|
||||
callback_name = node.arguments[0].value
|
||||
if seen_callback_names.key?(callback_name)
|
||||
add_offense(node, message: format(MSG, name: callback_name))
|
||||
else
|
||||
seen_callback_names[callback_name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def each_after_commit_callback(class_node)
|
||||
class_send_nodes(class_node).each do |node|
|
||||
yield node if after_commit_callback?(node) && named_callback?(node)
|
||||
end
|
||||
end
|
||||
|
||||
def class_send_nodes(class_node)
|
||||
class_def = class_node.body
|
||||
|
||||
return [] unless class_def
|
||||
|
||||
if class_def.send_type?
|
||||
[class_def]
|
||||
else
|
||||
class_def.each_child_node(:send).to_a
|
||||
end
|
||||
end
|
||||
|
||||
def after_commit_callback?(node)
|
||||
AFTER_COMMIT_CALLBACKS.include?(node.method_name)
|
||||
end
|
||||
|
||||
def named_callback?(node)
|
||||
name = node.first_argument
|
||||
return false unless name
|
||||
|
||||
name.sym_type?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that controllers subclass ApplicationController.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # good
|
||||
# class MyController < ApplicationController
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# # bad
|
||||
# class MyController < ActionController::Base
|
||||
# # ...
|
||||
# end
|
||||
class ApplicationController < Cop
|
||||
MSG = 'Controllers should subclass `ApplicationController`.'
|
||||
SUPERCLASS = 'ApplicationController'
|
||||
BASE_PATTERN = '(const (const nil? :ActionController) :Base)'
|
||||
|
||||
# rubocop:disable Layout/ClassStructure
|
||||
include RuboCop::Cop::EnforceSuperclass
|
||||
# rubocop:enable Layout/ClassStructure
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.source_range, self.class::SUPERCLASS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that jobs subclass ApplicationJob with Rails 5.0.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # good
|
||||
# class Rails5Job < ApplicationJob
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# # bad
|
||||
# class Rails4Job < ActiveJob::Base
|
||||
# # ...
|
||||
# end
|
||||
class ApplicationJob < Cop
|
||||
extend TargetRailsVersion
|
||||
|
||||
minimum_target_rails_version 5.0
|
||||
|
||||
MSG = 'Jobs should subclass `ApplicationJob`.'
|
||||
SUPERCLASS = 'ApplicationJob'
|
||||
BASE_PATTERN = '(const (const nil? :ActiveJob) :Base)'
|
||||
|
||||
# rubocop:disable Layout/ClassStructure
|
||||
include RuboCop::Cop::EnforceSuperclass
|
||||
# rubocop:enable Layout/ClassStructure
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.source_range, self.class::SUPERCLASS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that mailers subclass ApplicationMailer with Rails 5.0.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # good
|
||||
# class MyMailer < ApplicationMailer
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# # bad
|
||||
# class MyMailer < ActionMailer::Base
|
||||
# # ...
|
||||
# end
|
||||
class ApplicationMailer < Cop
|
||||
extend TargetRailsVersion
|
||||
|
||||
minimum_target_rails_version 5.0
|
||||
|
||||
MSG = 'Mailers should subclass `ApplicationMailer`.'
|
||||
SUPERCLASS = 'ApplicationMailer'
|
||||
BASE_PATTERN = '(const (const nil? :ActionMailer) :Base)'
|
||||
|
||||
# rubocop:disable Layout/ClassStructure
|
||||
include RuboCop::Cop::EnforceSuperclass
|
||||
# rubocop:enable Layout/ClassStructure
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.source_range, self.class::SUPERCLASS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that models subclass ApplicationRecord with Rails 5.0.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # good
|
||||
# class Rails5Model < ApplicationRecord
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# # bad
|
||||
# class Rails4Model < ActiveRecord::Base
|
||||
# # ...
|
||||
# end
|
||||
class ApplicationRecord < Cop
|
||||
extend TargetRailsVersion
|
||||
|
||||
minimum_target_rails_version 5.0
|
||||
|
||||
MSG = 'Models should subclass `ApplicationRecord`.'
|
||||
SUPERCLASS = 'ApplicationRecord'
|
||||
BASE_PATTERN = '(const (const nil? :ActiveRecord) :Base)'
|
||||
|
||||
# rubocop:disable Layout/ClassStructure
|
||||
include RuboCop::Cop::EnforceSuperclass
|
||||
# rubocop:enable Layout/ClassStructure
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.source_range, self.class::SUPERCLASS)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,44 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
#
|
||||
# Use `assert_not` instead of `assert !`.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# assert !x
|
||||
#
|
||||
# # good
|
||||
# assert_not x
|
||||
#
|
||||
class AssertNot < RuboCop::Cop::Cop
|
||||
MSG = 'Prefer `assert_not` over `assert !`.'
|
||||
|
||||
def_node_matcher :offensive?, '(send nil? :assert (send ... :!) ...)'
|
||||
|
||||
def on_send(node)
|
||||
add_offense(node) if offensive?(node)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
expression = node.loc.expression
|
||||
|
||||
lambda do |corrector|
|
||||
corrector.replace(
|
||||
expression,
|
||||
corrected_source(expression.source)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def corrected_source(source)
|
||||
source.gsub(/^assert(\(| ) *! */, 'assert_not\\1')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,102 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop looks for belongs_to associations where we control whether the
|
||||
# association is required via the deprecated `required` option instead.
|
||||
#
|
||||
# Since Rails 5, belongs_to associations are required by default and this
|
||||
# can be controlled through the use of `optional: true`.
|
||||
#
|
||||
# From the release notes:
|
||||
#
|
||||
# belongs_to will now trigger a validation error by default if the
|
||||
# association is not present. You can turn this off on a
|
||||
# per-association basis with optional: true. Also deprecate required
|
||||
# option in favor of optional for belongs_to. (Pull Request)
|
||||
#
|
||||
# In the case that the developer is doing `required: false`, we
|
||||
# definitely want to autocorrect to `optional: true`.
|
||||
#
|
||||
# However, without knowing whether they've set overridden the default
|
||||
# value of `config.active_record.belongs_to_required_by_default`, we
|
||||
# can't say whether it's safe to remove `required: true` or whether we
|
||||
# should replace it with `optional: false` (or, similarly, remove a
|
||||
# superfluous `optional: false`). Therefore, in the cases we're using
|
||||
# `required: true`, we'll simply invert it to `optional: false` and the
|
||||
# user can remove depending on their defaults.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# class Post < ApplicationRecord
|
||||
# belongs_to :blog, required: false
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# class Post < ApplicationRecord
|
||||
# belongs_to :blog, optional: true
|
||||
# end
|
||||
#
|
||||
# # bad
|
||||
# class Post < ApplicationRecord
|
||||
# belongs_to :blog, required: true
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# class Post < ApplicationRecord
|
||||
# belongs_to :blog, optional: false
|
||||
# end
|
||||
#
|
||||
# @see https://guides.rubyonrails.org/5_0_release_notes.html
|
||||
# @see https://github.com/rails/rails/pull/18937
|
||||
class BelongsTo < Cop
|
||||
extend TargetRailsVersion
|
||||
|
||||
minimum_target_rails_version 5.0
|
||||
|
||||
SUPERFLOUS_REQUIRE_FALSE_MSG =
|
||||
'You specified `required: false`, in Rails > 5.0 the required ' \
|
||||
'option is deprecated and you want to use `optional: true`.'
|
||||
|
||||
SUPERFLOUS_REQUIRE_TRUE_MSG =
|
||||
'You specified `required: true`, in Rails > 5.0 the required ' \
|
||||
'option is deprecated and you want to use `optional: false`. ' \
|
||||
'In most configurations, this is the default and you can omit ' \
|
||||
'this option altogether'
|
||||
|
||||
def_node_matcher :match_belongs_to_with_options, <<~PATTERN
|
||||
(send _ :belongs_to _
|
||||
(hash <$(pair (sym :required) ${true false}) ...>)
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
match_belongs_to_with_options(node) do |_option_node, option_value|
|
||||
message =
|
||||
if option_value.true_type?
|
||||
SUPERFLOUS_REQUIRE_TRUE_MSG
|
||||
elsif option_value.false_type?
|
||||
SUPERFLOUS_REQUIRE_FALSE_MSG
|
||||
end
|
||||
|
||||
add_offense(node, message: message, location: :selector)
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
option_node, option_value = match_belongs_to_with_options(node)
|
||||
return unless option_node
|
||||
|
||||
lambda do |corrector|
|
||||
if option_value.true_type?
|
||||
corrector.replace(option_node.loc.expression, 'optional: false')
|
||||
elsif option_value.false_type?
|
||||
corrector.replace(option_node.loc.expression, 'optional: true')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
164
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/blank.rb
vendored
Normal file
164
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/blank.rb
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks for code that can be written with simpler conditionals
|
||||
# using `Object#blank?` defined by Active Support.
|
||||
#
|
||||
# Interaction with `Style/UnlessElse`:
|
||||
# The configuration of `NotPresent` will not produce an offense in the
|
||||
# context of `unless else` if `Style/UnlessElse` is inabled. This is
|
||||
# to prevent interference between the auto-correction of the two cops.
|
||||
#
|
||||
# @example NilOrEmpty: true (default)
|
||||
# # Converts usages of `nil? || empty?` to `blank?`
|
||||
#
|
||||
# # bad
|
||||
# foo.nil? || foo.empty?
|
||||
# foo == nil || foo.empty?
|
||||
#
|
||||
# # good
|
||||
# foo.blank?
|
||||
#
|
||||
# @example NotPresent: true (default)
|
||||
# # Converts usages of `!present?` to `blank?`
|
||||
#
|
||||
# # bad
|
||||
# !foo.present?
|
||||
#
|
||||
# # good
|
||||
# foo.blank?
|
||||
#
|
||||
# @example UnlessPresent: true (default)
|
||||
# # Converts usages of `unless present?` to `if blank?`
|
||||
#
|
||||
# # bad
|
||||
# something unless foo.present?
|
||||
#
|
||||
# # good
|
||||
# something if foo.blank?
|
||||
#
|
||||
# # bad
|
||||
# unless foo.present?
|
||||
# something
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# if foo.blank?
|
||||
# something
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# def blank?
|
||||
# !present?
|
||||
# end
|
||||
class Blank < Cop
|
||||
MSG_NIL_OR_EMPTY = 'Use `%<prefer>s` instead of `%<current>s`.'
|
||||
MSG_NOT_PRESENT = 'Use `%<prefer>s` instead of `%<current>s`.'
|
||||
MSG_UNLESS_PRESENT = 'Use `if %<prefer>s` instead of ' \
|
||||
'`%<current>s`.'
|
||||
|
||||
# `(send nil $_)` is not actually a valid match for an offense. Nodes
|
||||
# that have a single method call on the left hand side
|
||||
# (`bar || foo.empty?`) will blow up when checking
|
||||
# `(send (:nil) :== $_)`.
|
||||
def_node_matcher :nil_or_empty?, <<~PATTERN
|
||||
(or
|
||||
{
|
||||
(send $_ :!)
|
||||
(send $_ :nil?)
|
||||
(send $_ :== nil)
|
||||
(send nil :== $_)
|
||||
}
|
||||
{
|
||||
(send $_ :empty?)
|
||||
(send (send (send $_ :empty?) :!) :!)
|
||||
}
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :not_present?, '(send (send $_ :present?) :!)'
|
||||
|
||||
def_node_matcher :defining_blank?, '(def :blank? (args) ...)'
|
||||
|
||||
def_node_matcher :unless_present?, <<~PATTERN
|
||||
(:if $(send $_ :present?) {nil? (...)} ...)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless cop_config['NotPresent']
|
||||
|
||||
not_present?(node) do |receiver|
|
||||
# accepts !present? if its in the body of a `blank?` method
|
||||
next if defining_blank?(node.parent)
|
||||
|
||||
add_offense(node,
|
||||
message: format(MSG_NOT_PRESENT,
|
||||
prefer: replacement(receiver),
|
||||
current: node.source))
|
||||
end
|
||||
end
|
||||
|
||||
def on_or(node)
|
||||
return unless cop_config['NilOrEmpty']
|
||||
|
||||
nil_or_empty?(node) do |var1, var2|
|
||||
return unless var1 == var2
|
||||
|
||||
add_offense(node,
|
||||
message: format(MSG_NIL_OR_EMPTY,
|
||||
prefer: replacement(var1),
|
||||
current: node.source))
|
||||
end
|
||||
end
|
||||
|
||||
def on_if(node)
|
||||
return unless cop_config['UnlessPresent']
|
||||
return unless node.unless?
|
||||
return if node.else? && config.for_cop('Style/UnlessElse')['Enabled']
|
||||
|
||||
unless_present?(node) do |method_call, receiver|
|
||||
range = unless_condition(node, method_call)
|
||||
|
||||
add_offense(node,
|
||||
location: range,
|
||||
message: format(MSG_UNLESS_PRESENT,
|
||||
prefer: replacement(receiver),
|
||||
current: range.source))
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
method_call, variable1 = unless_present?(node)
|
||||
|
||||
if method_call
|
||||
corrector.replace(node.loc.keyword, 'if')
|
||||
range = method_call.loc.expression
|
||||
else
|
||||
variable1, _variable2 = nil_or_empty?(node) || not_present?(node)
|
||||
range = node.loc.expression
|
||||
end
|
||||
|
||||
corrector.replace(range, replacement(variable1))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unless_condition(node, method_call)
|
||||
if node.modifier_form?
|
||||
node.loc.keyword.join(node.loc.expression.end)
|
||||
else
|
||||
node.loc.expression.begin.join(method_call.loc.expression)
|
||||
end
|
||||
end
|
||||
|
||||
def replacement(node)
|
||||
node.respond_to?(:source) ? "#{node.source}.blank?" : 'blank?'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,293 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This Cop checks whether alter queries are combinable.
|
||||
# If combinable queries are detected, it suggests to you
|
||||
# to use `change_table` with `bulk: true` instead.
|
||||
# This option causes the migration to generate a single
|
||||
# ALTER TABLE statement combining multiple column alterations.
|
||||
#
|
||||
# The `bulk` option is only supported on the MySQL and
|
||||
# the PostgreSQL (5.2 later) adapter; thus it will
|
||||
# automatically detect an adapter from `development` environment
|
||||
# in `config/database.yml` when the `Database` option is not set.
|
||||
# If the adapter is not `mysql2` or `postgresql`,
|
||||
# this Cop ignores offenses.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# def change
|
||||
# add_column :users, :name, :string, null: false
|
||||
# add_column :users, :nickname, :string
|
||||
#
|
||||
# # ALTER TABLE `users` ADD `name` varchar(255) NOT NULL
|
||||
# # ALTER TABLE `users` ADD `nickname` varchar(255)
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# def change
|
||||
# change_table :users, bulk: true do |t|
|
||||
# t.string :name, null: false
|
||||
# t.string :nickname
|
||||
# end
|
||||
#
|
||||
# # ALTER TABLE `users` ADD `name` varchar(255) NOT NULL,
|
||||
# # ADD `nickname` varchar(255)
|
||||
# end
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# def change
|
||||
# change_table :users do |t|
|
||||
# t.string :name, null: false
|
||||
# t.string :nickname
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# def change
|
||||
# change_table :users, bulk: true do |t|
|
||||
# t.string :name, null: false
|
||||
# t.string :nickname
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# # When you don't want to combine alter queries.
|
||||
# def change
|
||||
# change_table :users, bulk: false do |t|
|
||||
# t.string :name, null: false
|
||||
# t.string :nickname
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table
|
||||
# @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
|
||||
class BulkChangeTable < Cop
|
||||
MSG_FOR_CHANGE_TABLE = <<~MSG.chomp
|
||||
You can combine alter queries using `bulk: true` options.
|
||||
MSG
|
||||
MSG_FOR_ALTER_METHODS = <<~MSG.chomp
|
||||
You can use `change_table :%<table>s, bulk: true` to combine alter queries.
|
||||
MSG
|
||||
|
||||
MYSQL = 'mysql'
|
||||
POSTGRESQL = 'postgresql'
|
||||
|
||||
MIGRATION_METHODS = %i[change up down].freeze
|
||||
|
||||
COMBINABLE_TRANSFORMATIONS = %i[
|
||||
primary_key
|
||||
column
|
||||
string
|
||||
text
|
||||
integer
|
||||
bigint
|
||||
float
|
||||
decimal
|
||||
numeric
|
||||
datetime
|
||||
timestamp
|
||||
time
|
||||
date
|
||||
binary
|
||||
boolean
|
||||
json
|
||||
virtual
|
||||
remove
|
||||
change
|
||||
timestamps
|
||||
remove_timestamps
|
||||
].freeze
|
||||
|
||||
COMBINABLE_ALTER_METHODS = %i[
|
||||
add_column
|
||||
remove_column
|
||||
remove_columns
|
||||
change_column
|
||||
add_timestamps
|
||||
remove_timestamps
|
||||
].freeze
|
||||
|
||||
MYSQL_COMBINABLE_TRANSFORMATIONS = %i[
|
||||
rename
|
||||
index
|
||||
remove_index
|
||||
].freeze
|
||||
|
||||
MYSQL_COMBINABLE_ALTER_METHODS = %i[
|
||||
rename_column
|
||||
add_index
|
||||
remove_index
|
||||
].freeze
|
||||
|
||||
POSTGRESQL_COMBINABLE_TRANSFORMATIONS = %i[
|
||||
change_default
|
||||
].freeze
|
||||
|
||||
POSTGRESQL_COMBINABLE_ALTER_METHODS = %i[
|
||||
change_column_default
|
||||
].freeze
|
||||
|
||||
def on_def(node)
|
||||
return unless support_bulk_alter?
|
||||
return unless MIGRATION_METHODS.include?(node.method_name)
|
||||
return unless node.body
|
||||
|
||||
recorder = AlterMethodsRecorder.new
|
||||
|
||||
node.body.child_nodes.each do |child_node|
|
||||
if call_to_combinable_alter_method? child_node
|
||||
recorder.process(child_node)
|
||||
else
|
||||
recorder.flush
|
||||
end
|
||||
end
|
||||
|
||||
recorder.offensive_nodes.each { |n| add_offense_for_alter_methods(n) }
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
return unless support_bulk_alter?
|
||||
return unless node.command?(:change_table)
|
||||
return if include_bulk_options?(node)
|
||||
return unless node.block_node
|
||||
|
||||
send_nodes = node.block_node.body.each_child_node(:send).to_a
|
||||
|
||||
transformations = send_nodes.select do |send_node|
|
||||
combinable_transformations.include?(send_node.method_name)
|
||||
end
|
||||
|
||||
add_offense_for_change_table(node) if transformations.size > 1
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @param node [RuboCop::AST::SendNode] (send nil? :change_table ...)
|
||||
def include_bulk_options?(node)
|
||||
# arguments: [{(sym :table)(str "table")} (hash (pair (sym :bulk) _))]
|
||||
options = node.arguments[1]
|
||||
return false unless options
|
||||
|
||||
options.hash_type? &&
|
||||
options.keys.any? { |key| key.sym_type? && key.value == :bulk }
|
||||
end
|
||||
|
||||
def database
|
||||
cop_config['Database'] || database_from_yaml
|
||||
end
|
||||
|
||||
def database_from_yaml
|
||||
return nil unless database_yaml
|
||||
|
||||
case database_yaml['adapter']
|
||||
when 'mysql2'
|
||||
MYSQL
|
||||
when 'postgresql'
|
||||
POSTGRESQL
|
||||
end
|
||||
end
|
||||
|
||||
def database_yaml
|
||||
return nil unless File.exist?('config/database.yml')
|
||||
|
||||
yaml = YAML.load_file('config/database.yml')
|
||||
return nil unless yaml.is_a? Hash
|
||||
|
||||
config = yaml['development']
|
||||
return nil unless config.is_a?(Hash)
|
||||
|
||||
config
|
||||
rescue Psych::SyntaxError
|
||||
nil
|
||||
end
|
||||
|
||||
def support_bulk_alter?
|
||||
case database
|
||||
when MYSQL
|
||||
true
|
||||
when POSTGRESQL
|
||||
# Add bulk alter support for PostgreSQL in 5.2.0
|
||||
# @see https://github.com/rails/rails/pull/31331
|
||||
target_rails_version >= 5.2
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def call_to_combinable_alter_method?(child_node)
|
||||
child_node.send_type? &&
|
||||
combinable_alter_methods.include?(child_node.method_name)
|
||||
end
|
||||
|
||||
def combinable_alter_methods
|
||||
case database
|
||||
when MYSQL
|
||||
COMBINABLE_ALTER_METHODS + MYSQL_COMBINABLE_ALTER_METHODS
|
||||
when POSTGRESQL
|
||||
COMBINABLE_ALTER_METHODS + POSTGRESQL_COMBINABLE_ALTER_METHODS
|
||||
end
|
||||
end
|
||||
|
||||
def combinable_transformations
|
||||
case database
|
||||
when MYSQL
|
||||
COMBINABLE_TRANSFORMATIONS + MYSQL_COMBINABLE_TRANSFORMATIONS
|
||||
when POSTGRESQL
|
||||
COMBINABLE_TRANSFORMATIONS + POSTGRESQL_COMBINABLE_TRANSFORMATIONS
|
||||
end
|
||||
end
|
||||
|
||||
# @param node [RuboCop::AST::SendNode]
|
||||
def add_offense_for_alter_methods(node)
|
||||
# arguments: [{(sym :table)(str "table")} ...]
|
||||
table_node = node.arguments[0]
|
||||
return unless table_node.is_a? RuboCop::AST::BasicLiteralNode
|
||||
|
||||
message = format(MSG_FOR_ALTER_METHODS, table: table_node.value)
|
||||
add_offense(node, message: message)
|
||||
end
|
||||
|
||||
# @param node [RuboCop::AST::SendNode]
|
||||
def add_offense_for_change_table(node)
|
||||
add_offense(node, message: MSG_FOR_CHANGE_TABLE)
|
||||
end
|
||||
|
||||
# Record combinable alter methods and register offensive nodes.
|
||||
class AlterMethodsRecorder
|
||||
def initialize
|
||||
@nodes = []
|
||||
@offensive_nodes = []
|
||||
end
|
||||
|
||||
# @param new_node [RuboCop::AST::SendNode]
|
||||
def process(new_node)
|
||||
# arguments: [{(sym :table)(str "table")} ...]
|
||||
table_node = new_node.arguments[0]
|
||||
if table_node.is_a? RuboCop::AST::BasicLiteralNode
|
||||
flush unless @nodes.all? do |node|
|
||||
node.arguments[0].value.to_s == table_node.value.to_s
|
||||
end
|
||||
@nodes << new_node
|
||||
else
|
||||
flush
|
||||
end
|
||||
end
|
||||
|
||||
def flush
|
||||
@offensive_nodes << @nodes.first if @nodes.size > 1
|
||||
@nodes = []
|
||||
end
|
||||
|
||||
def offensive_nodes
|
||||
flush
|
||||
@offensive_nodes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that `tag` is used instead of `content_tag`
|
||||
# because `content_tag` is legacy syntax.
|
||||
#
|
||||
# NOTE: Allow `content_tag` when the first argument is a variable because
|
||||
# `content_tag(name)` is simpler rather than `tag.public_send(name)`.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# content_tag(:p, 'Hello world!')
|
||||
# content_tag(:br)
|
||||
#
|
||||
# # good
|
||||
# tag.p('Hello world!')
|
||||
# tag.br
|
||||
# content_tag(name, 'Hello world!')
|
||||
class ContentTag < Cop
|
||||
include RangeHelp
|
||||
extend TargetRailsVersion
|
||||
|
||||
minimum_target_rails_version 5.1
|
||||
|
||||
MSG = 'Use `tag` instead of `content_tag`.'
|
||||
|
||||
def on_send(node)
|
||||
return unless node.method?(:content_tag)
|
||||
|
||||
first_argument = node.first_argument
|
||||
return unless first_argument
|
||||
|
||||
return if first_argument.variable? || first_argument.send_type? || first_argument.const_type?
|
||||
|
||||
add_offense(node)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
if method_name?(node.first_argument)
|
||||
range = correction_range(node)
|
||||
|
||||
rest_args = node.arguments.drop(1)
|
||||
replacement = "tag.#{node.first_argument.value.to_s.underscore}(#{rest_args.map(&:source).join(', ')})"
|
||||
|
||||
corrector.replace(range, replacement)
|
||||
else
|
||||
corrector.replace(node.loc.selector, 'tag')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def method_name?(node)
|
||||
return false unless node.str_type? || node.sym_type?
|
||||
|
||||
/^[a-zA-Z_][a-zA-Z_\-0-9]*$/.match?(node.value)
|
||||
end
|
||||
|
||||
def correction_range(node)
|
||||
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks the migration for which timestamps are not included
|
||||
# when creating a new table.
|
||||
# In many cases, timestamps are useful information and should be added.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# create_table :users
|
||||
#
|
||||
# # bad
|
||||
# create_table :users do |t|
|
||||
# t.string :name
|
||||
# t.string :email
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# create_table :users do |t|
|
||||
# t.string :name
|
||||
# t.string :email
|
||||
#
|
||||
# t.timestamps
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# create_table :users do |t|
|
||||
# t.string :name
|
||||
# t.string :email
|
||||
#
|
||||
# t.datetime :created_at, default: -> { 'CURRENT_TIMESTAMP' }
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# create_table :users do |t|
|
||||
# t.string :name
|
||||
# t.string :email
|
||||
#
|
||||
# t.datetime :updated_at, default: -> { 'CURRENT_TIMESTAMP' }
|
||||
# end
|
||||
class CreateTableWithTimestamps < Cop
|
||||
MSG = 'Add timestamps when creating a new table.'
|
||||
|
||||
def_node_matcher :create_table_with_block?, <<~PATTERN
|
||||
(block
|
||||
(send nil? :create_table ...)
|
||||
(args (arg _var))
|
||||
_)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :create_table_with_timestamps_proc?, <<~PATTERN
|
||||
(send nil? :create_table (sym _) ... (block-pass (sym :timestamps)))
|
||||
PATTERN
|
||||
|
||||
def_node_search :timestamps_included?, <<~PATTERN
|
||||
(send _var :timestamps ...)
|
||||
PATTERN
|
||||
|
||||
def_node_search :created_at_or_updated_at_included?, <<~PATTERN
|
||||
(send _var :datetime
|
||||
{(sym {:created_at :updated_at})(str {"created_at" "updated_at"})}
|
||||
...)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless node.command?(:create_table)
|
||||
|
||||
parent = node.parent
|
||||
|
||||
if create_table_with_block?(parent)
|
||||
add_offense(parent) if parent.body.nil? || !time_columns_included?(parent.body)
|
||||
elsif create_table_with_timestamps_proc?(node)
|
||||
# nothing to do
|
||||
else
|
||||
add_offense(node)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def time_columns_included?(node)
|
||||
timestamps_included?(node) || created_at_or_updated_at_included?(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
161
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/date.rb
vendored
Normal file
161
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/date.rb
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks for the correct use of Date methods,
|
||||
# such as Date.today, Date.current etc.
|
||||
#
|
||||
# Using `Date.today` is dangerous, because it doesn't know anything about
|
||||
# Rails time zone. You must use `Time.zone.today` instead.
|
||||
#
|
||||
# The cop also reports warnings when you are using `to_time` method,
|
||||
# because it doesn't know about Rails time zone either.
|
||||
#
|
||||
# Two styles are supported for this cop. When EnforcedStyle is 'strict'
|
||||
# then the Date methods `today`, `current`, `yesterday`, and `tomorrow`
|
||||
# are prohibited and the usage of both `to_time`
|
||||
# and 'to_time_in_current_zone' are reported as warning.
|
||||
#
|
||||
# When EnforcedStyle is 'flexible' then only `Date.today` is prohibited
|
||||
# and only `to_time` is reported as warning.
|
||||
#
|
||||
# @example EnforcedStyle: strict
|
||||
# # bad
|
||||
# Date.current
|
||||
# Date.yesterday
|
||||
# Date.today
|
||||
# date.to_time
|
||||
#
|
||||
# # good
|
||||
# Time.zone.today
|
||||
# Time.zone.today - 1.day
|
||||
#
|
||||
# @example EnforcedStyle: flexible (default)
|
||||
# # bad
|
||||
# Date.today
|
||||
# date.to_time
|
||||
#
|
||||
# # good
|
||||
# Time.zone.today
|
||||
# Time.zone.today - 1.day
|
||||
# Date.current
|
||||
# Date.yesterday
|
||||
# date.in_time_zone
|
||||
#
|
||||
class Date < Cop
|
||||
include ConfigurableEnforcedStyle
|
||||
|
||||
MSG = 'Do not use `Date.%<method_called>s` without zone. Use ' \
|
||||
'`Time.zone.%<day>s` instead.'
|
||||
|
||||
MSG_SEND = 'Do not use `%<method>s` on Date objects, because they ' \
|
||||
'know nothing about the time zone in use.'
|
||||
|
||||
BAD_DAYS = %i[today current yesterday tomorrow].freeze
|
||||
|
||||
DEPRECATED_METHODS = [
|
||||
{ deprecated: 'to_time_in_current_zone', relevant: 'in_time_zone' }
|
||||
].freeze
|
||||
|
||||
DEPRECATED_MSG = '`%<deprecated>s` is deprecated. ' \
|
||||
'Use `%<relevant>s` instead.'
|
||||
|
||||
def on_const(node)
|
||||
mod, klass = *node.children
|
||||
# we should only check core Date class (`Date` or `::Date`)
|
||||
return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
|
||||
|
||||
check_date_node(node.parent) if klass == :Date
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
return unless node.receiver && bad_methods.include?(node.method_name)
|
||||
|
||||
return if safe_chain?(node) || safe_to_time?(node)
|
||||
|
||||
check_deprecated_methods(node)
|
||||
|
||||
add_offense(node, location: :selector,
|
||||
message: format(MSG_SEND, method: node.method_name))
|
||||
end
|
||||
alias on_csend on_send
|
||||
|
||||
private
|
||||
|
||||
def check_deprecated_methods(node)
|
||||
DEPRECATED_METHODS.each do |method|
|
||||
next unless node.method?(method[:deprecated].to_sym)
|
||||
|
||||
add_offense(node, location: :selector,
|
||||
message: format(DEPRECATED_MSG,
|
||||
deprecated: method[:deprecated],
|
||||
relevant: method[:relevant]))
|
||||
end
|
||||
end
|
||||
|
||||
def check_date_node(node)
|
||||
chain = extract_method_chain(node)
|
||||
|
||||
return if (chain & bad_days).empty?
|
||||
|
||||
method_name = (chain & bad_days).join('.')
|
||||
|
||||
day = method_name
|
||||
day = 'today' if method_name == 'current'
|
||||
|
||||
add_offense(node, location: :selector,
|
||||
message: format(MSG,
|
||||
method_called: method_name,
|
||||
day: day))
|
||||
end
|
||||
|
||||
def extract_method_chain(node)
|
||||
[node, *node.each_ancestor(:send)].map(&:method_name)
|
||||
end
|
||||
|
||||
# checks that parent node of send_type
|
||||
# and receiver is the given node
|
||||
def method_send?(node)
|
||||
return false unless node.parent&.send_type?
|
||||
|
||||
node.parent.receiver == node
|
||||
end
|
||||
|
||||
def safe_chain?(node)
|
||||
chain = extract_method_chain(node)
|
||||
|
||||
(chain & bad_methods).empty? || !(chain & good_methods).empty?
|
||||
end
|
||||
|
||||
def safe_to_time?(node)
|
||||
return unless node.method?(:to_time)
|
||||
|
||||
if node.receiver.str_type?
|
||||
zone_regexp = /([+-][\d:]+|\dZ)\z/
|
||||
|
||||
node.receiver.str_content.match(zone_regexp)
|
||||
else
|
||||
node.arguments.one?
|
||||
end
|
||||
end
|
||||
|
||||
def good_days
|
||||
style == :strict ? [] : %i[current yesterday tomorrow]
|
||||
end
|
||||
|
||||
def bad_days
|
||||
BAD_DAYS - good_days
|
||||
end
|
||||
|
||||
def bad_methods
|
||||
%i[to_time to_time_in_current_zone]
|
||||
end
|
||||
|
||||
def good_methods
|
||||
style == :strict ? [] : TimeZone::ACCEPTED_METHODS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop looks for uses of `default_scope`.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# default_scope -> { where(hidden: false) }
|
||||
#
|
||||
# # good
|
||||
# scope :published, -> { where(hidden: false) }
|
||||
#
|
||||
# # bad
|
||||
# def self.default_scope
|
||||
# where(hidden: false)
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# def self.published
|
||||
# where(hidden: false)
|
||||
# end
|
||||
#
|
||||
class DefaultScope < Cop
|
||||
MSG = 'Avoid use of `default_scope`. It is better to use explicitly named scopes.'
|
||||
|
||||
def_node_matcher :method_call?, <<~PATTERN
|
||||
(send nil? :default_scope ...)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :class_method_definition?, <<~PATTERN
|
||||
(defs _ :default_scope args ...)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :eigenclass_method_definition?, <<~PATTERN
|
||||
(sclass (self) $(def :default_scope args ...))
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
add_offense(node, location: :selector) if method_call?(node)
|
||||
end
|
||||
|
||||
def on_defs(node)
|
||||
add_offense(node, location: :name) if class_method_definition?(node)
|
||||
end
|
||||
|
||||
def on_sclass(node)
|
||||
eigenclass_method_definition?(node) { |default_scope| add_offense(default_scope, location: :name) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
130
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/delegate.rb
vendored
Normal file
130
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/delegate.rb
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop looks for delegations that could have been created
|
||||
# automatically with the `delegate` method.
|
||||
#
|
||||
# Safe navigation `&.` is ignored because Rails' `allow_nil`
|
||||
# option checks not just for nil but also delegates if nil
|
||||
# responds to the delegated method.
|
||||
#
|
||||
# The `EnforceForPrefixed` option (defaulted to `true`) means that
|
||||
# using the target object as a prefix of the method name
|
||||
# without using the `delegate` method will be a violation.
|
||||
# When set to `false`, this case is legal.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# def bar
|
||||
# foo.bar
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# delegate :bar, to: :foo
|
||||
#
|
||||
# # good
|
||||
# def bar
|
||||
# foo&.bar
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# private
|
||||
# def bar
|
||||
# foo.bar
|
||||
# end
|
||||
#
|
||||
# @example EnforceForPrefixed: true (default)
|
||||
# # bad
|
||||
# def foo_bar
|
||||
# foo.bar
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# delegate :bar, to: :foo, prefix: true
|
||||
#
|
||||
# @example EnforceForPrefixed: false
|
||||
# # good
|
||||
# def foo_bar
|
||||
# foo.bar
|
||||
# end
|
||||
#
|
||||
# # good
|
||||
# delegate :bar, to: :foo, prefix: true
|
||||
class Delegate < Cop
|
||||
MSG = 'Use `delegate` to define delegations.'
|
||||
|
||||
def_node_matcher :delegate?, <<~PATTERN
|
||||
(def _method_name _args
|
||||
(send (send nil? _) _ ...))
|
||||
PATTERN
|
||||
|
||||
def on_def(node)
|
||||
return unless trivial_delegate?(node)
|
||||
return if private_or_protected_delegation(node)
|
||||
|
||||
add_offense(node, location: :keyword)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
delegation = ["delegate :#{node.body.method_name}",
|
||||
"to: :#{node.body.receiver.method_name}"]
|
||||
|
||||
delegation << ['prefix: true'] if node.method?(prefixed_method_name(node.body))
|
||||
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.source_range, delegation.join(', '))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def trivial_delegate?(def_node)
|
||||
delegate?(def_node) &&
|
||||
method_name_matches?(def_node.method_name, def_node.body) &&
|
||||
arguments_match?(def_node.arguments, def_node.body)
|
||||
end
|
||||
|
||||
def arguments_match?(arg_array, body)
|
||||
argument_array = body.arguments
|
||||
|
||||
return false if arg_array.size != argument_array.size
|
||||
|
||||
arg_array.zip(argument_array).all? do |arg, argument|
|
||||
arg.arg_type? &&
|
||||
argument.lvar_type? &&
|
||||
arg.children == argument.children
|
||||
end
|
||||
end
|
||||
|
||||
def method_name_matches?(method_name, body)
|
||||
method_name == body.method_name ||
|
||||
include_prefix_case? && method_name == prefixed_method_name(body)
|
||||
end
|
||||
|
||||
def include_prefix_case?
|
||||
cop_config['EnforceForPrefixed']
|
||||
end
|
||||
|
||||
def prefixed_method_name(body)
|
||||
[body.receiver.method_name, body.method_name].join('_').to_sym
|
||||
end
|
||||
|
||||
def private_or_protected_delegation(node)
|
||||
line = node.first_line
|
||||
private_or_protected_before(line) ||
|
||||
private_or_protected_inline(line)
|
||||
end
|
||||
|
||||
def private_or_protected_before(line)
|
||||
(processed_source[0..line].map(&:strip) & %w[private protected]).any?
|
||||
end
|
||||
|
||||
def private_or_protected_inline(line)
|
||||
processed_source[line - 1].strip.match?(/\A(private )|(protected )/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop looks for delegations that pass :allow_blank as an option
|
||||
# instead of :allow_nil. :allow_blank is not a valid option to pass
|
||||
# to ActiveSupport#delegate.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# delegate :foo, to: :bar, allow_blank: true
|
||||
#
|
||||
# # good
|
||||
# delegate :foo, to: :bar, allow_nil: true
|
||||
class DelegateAllowBlank < Cop
|
||||
MSG = '`allow_blank` is not a valid option, use `allow_nil`.'
|
||||
|
||||
def_node_matcher :allow_blank_option, <<~PATTERN
|
||||
(send nil? :delegate _ (hash <$(pair (sym :allow_blank) true) ...>))
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
allow_blank_option(node) do |offending_node|
|
||||
add_offense(offending_node)
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(pair_node)
|
||||
lambda do |corrector|
|
||||
corrector.replace(pair_node.key.source_range, 'allow_nil')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,117 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks dynamic `find_by_*` methods.
|
||||
# Use `find_by` instead of dynamic method.
|
||||
# See. https://rails.rubystyle.guide#find_by
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# User.find_by_name(name)
|
||||
# User.find_by_name_and_email(name)
|
||||
# User.find_by_email!(name)
|
||||
#
|
||||
# # good
|
||||
# User.find_by(name: name)
|
||||
# User.find_by(name: name, email: email)
|
||||
# User.find_by!(email: email)
|
||||
#
|
||||
# @example AllowedMethods: find_by_sql
|
||||
# # bad
|
||||
# User.find_by_query(users_query)
|
||||
#
|
||||
# # good
|
||||
# User.find_by_sql(users_sql)
|
||||
#
|
||||
# @example AllowedReceivers: Gem::Specification
|
||||
# # bad
|
||||
# Specification.find_by_name('backend').gem_dir
|
||||
#
|
||||
# # good
|
||||
# Gem::Specification.find_by_name('backend').gem_dir
|
||||
class DynamicFindBy < Cop
|
||||
MSG = 'Use `%<static_name>s` instead of dynamic `%<method>s`.'
|
||||
METHOD_PATTERN = /^find_by_(.+?)(!)?$/.freeze
|
||||
|
||||
def on_send(node)
|
||||
return if allowed_invocation?(node)
|
||||
|
||||
method_name = node.method_name
|
||||
static_name = static_method_name(method_name)
|
||||
return unless static_name
|
||||
return if node.arguments.any?(&:splat_type?)
|
||||
|
||||
add_offense(node,
|
||||
message: format(MSG, static_name: static_name,
|
||||
method: method_name))
|
||||
end
|
||||
alias on_csend on_send
|
||||
|
||||
def autocorrect(node)
|
||||
keywords = column_keywords(node.method_name)
|
||||
|
||||
return if keywords.size != node.arguments.size
|
||||
|
||||
lambda do |corrector|
|
||||
autocorrect_method_name(corrector, node)
|
||||
autocorrect_argument_keywords(corrector, node, keywords)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed_invocation?(node)
|
||||
allowed_method?(node) || allowed_receiver?(node) ||
|
||||
whitelisted?(node)
|
||||
end
|
||||
|
||||
def allowed_method?(node)
|
||||
return unless cop_config['AllowedMethods']
|
||||
|
||||
cop_config['AllowedMethods'].include?(node.method_name.to_s)
|
||||
end
|
||||
|
||||
def allowed_receiver?(node)
|
||||
return unless cop_config['AllowedReceivers'] && node.receiver
|
||||
|
||||
cop_config['AllowedReceivers'].include?(node.receiver.source)
|
||||
end
|
||||
|
||||
# config option `WhiteList` will be deprecated soon
|
||||
def whitelisted?(node)
|
||||
whitelist_config = cop_config['Whitelist']
|
||||
return unless whitelist_config
|
||||
|
||||
whitelist_config.include?(node.method_name.to_s)
|
||||
end
|
||||
|
||||
def autocorrect_method_name(corrector, node)
|
||||
corrector.replace(node.loc.selector,
|
||||
static_method_name(node.method_name.to_s))
|
||||
end
|
||||
|
||||
def autocorrect_argument_keywords(corrector, node, keywords)
|
||||
keywords.each.with_index do |keyword, idx|
|
||||
corrector.insert_before(node.arguments[idx].loc.expression, keyword)
|
||||
end
|
||||
end
|
||||
|
||||
def column_keywords(method)
|
||||
keyword_string = method.to_s[METHOD_PATTERN, 1]
|
||||
keyword_string.split('_and_').map { |keyword| "#{keyword}: " }
|
||||
end
|
||||
|
||||
# Returns static method name.
|
||||
# If code isn't wrong, returns nil
|
||||
def static_method_name(method_name)
|
||||
match = METHOD_PATTERN.match(method_name)
|
||||
return nil unless match
|
||||
|
||||
match[2] ? 'find_by!' : 'find_by'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop looks for enums written with array syntax.
|
||||
#
|
||||
# When using array syntax, adding an element in a
|
||||
# position other than the last causes all previous
|
||||
# definitions to shift. Explicitly specifying the
|
||||
# value for each key prevents this from happening.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# enum status: [:active, :archived]
|
||||
#
|
||||
# # good
|
||||
# enum status: { active: 0, archived: 1 }
|
||||
#
|
||||
class EnumHash < Cop
|
||||
MSG = 'Enum defined as an array found in `%<enum>s` enum declaration. '\
|
||||
'Use hash syntax instead.'
|
||||
|
||||
def_node_matcher :enum?, <<~PATTERN
|
||||
(send nil? :enum (hash $...))
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :array_pair?, <<~PATTERN
|
||||
(pair $_ $array)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
enum?(node) do |pairs|
|
||||
pairs.each do |pair|
|
||||
key, array = array_pair?(pair)
|
||||
next unless key
|
||||
|
||||
add_offense(array, message: format(MSG, enum: enum_name(key)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
hash = node.children.each_with_index.map do |elem, index|
|
||||
"#{source(elem)} => #{index}"
|
||||
end.join(', ')
|
||||
|
||||
->(corrector) { corrector.replace(node.loc.expression, "{#{hash}}") }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enum_name(key)
|
||||
case key.type
|
||||
when :sym, :str
|
||||
key.value
|
||||
else
|
||||
key.source
|
||||
end
|
||||
end
|
||||
|
||||
def source(elem)
|
||||
case elem.type
|
||||
when :str
|
||||
elem.value.dump
|
||||
when :sym
|
||||
elem.value.inspect
|
||||
else
|
||||
elem.source
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop looks for duplicate values in enum declarations.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# enum status: { active: 0, archived: 0 }
|
||||
#
|
||||
# # good
|
||||
# enum status: { active: 0, archived: 1 }
|
||||
#
|
||||
# # bad
|
||||
# enum status: [:active, :archived, :active]
|
||||
#
|
||||
# # good
|
||||
# enum status: [:active, :archived]
|
||||
class EnumUniqueness < Cop
|
||||
include Duplication
|
||||
|
||||
MSG = 'Duplicate value `%<value>s` found in `%<enum>s` ' \
|
||||
'enum declaration.'
|
||||
|
||||
def_node_matcher :enum?, <<~PATTERN
|
||||
(send nil? :enum (hash $...))
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :enum_values, <<~PATTERN
|
||||
(pair $_ ${array hash})
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
enum?(node) do |pairs|
|
||||
pairs.each do |pair|
|
||||
enum_values(pair) do |key, args|
|
||||
items = args.values
|
||||
|
||||
next unless duplicates?(items)
|
||||
|
||||
consecutive_duplicates(items).each do |item|
|
||||
add_offense(item, message: format(
|
||||
MSG, value: item.source, enum: enum_name(key)
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enum_name(key)
|
||||
case key.type
|
||||
when :sym, :str
|
||||
key.value
|
||||
else
|
||||
key.source
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,114 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop checks that Rails.env is compared using `.production?`-like
|
||||
# methods instead of equality against a string or symbol.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# Rails.env == 'production'
|
||||
#
|
||||
# # bad, always returns false
|
||||
# Rails.env == :test
|
||||
#
|
||||
# # good
|
||||
# Rails.env.production?
|
||||
class EnvironmentComparison < Cop
|
||||
MSG = 'Favor `%<bang>sRails.env.%<env>s?` over `%<source>s`.'
|
||||
|
||||
SYM_MSG = 'Do not compare `Rails.env` with a symbol, it will always ' \
|
||||
'evaluate to `false`.'
|
||||
|
||||
def_node_matcher :comparing_str_env_with_rails_env_on_lhs?, <<~PATTERN
|
||||
(send
|
||||
(send (const {nil? cbase} :Rails) :env)
|
||||
{:== :!=}
|
||||
$str
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :comparing_str_env_with_rails_env_on_rhs?, <<~PATTERN
|
||||
(send
|
||||
$str
|
||||
{:== :!=}
|
||||
(send (const {nil? cbase} :Rails) :env)
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :comparing_sym_env_with_rails_env_on_lhs?, <<~PATTERN
|
||||
(send
|
||||
(send (const {nil? cbase} :Rails) :env)
|
||||
{:== :!=}
|
||||
$sym
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :comparing_sym_env_with_rails_env_on_rhs?, <<~PATTERN
|
||||
(send
|
||||
$sym
|
||||
{:== :!=}
|
||||
(send (const {nil? cbase} :Rails) :env)
|
||||
)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :content, <<~PATTERN
|
||||
({str sym} $_)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
if (env_node = comparing_str_env_with_rails_env_on_lhs?(node) ||
|
||||
comparing_str_env_with_rails_env_on_rhs?(node))
|
||||
env, = *env_node
|
||||
bang = node.method?(:!=) ? '!' : ''
|
||||
|
||||
add_offense(node, message: format(
|
||||
MSG, bang: bang, env: env, source: node.source
|
||||
))
|
||||
end
|
||||
|
||||
if comparing_sym_env_with_rails_env_on_lhs?(node) ||
|
||||
comparing_sym_env_with_rails_env_on_rhs?(node)
|
||||
add_offense(node, message: SYM_MSG)
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
lambda do |corrector|
|
||||
replacement = build_predicate_method(node)
|
||||
|
||||
corrector.replace(node.source_range, replacement)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_predicate_method(node)
|
||||
if rails_env_on_lhs?(node)
|
||||
build_predicate_method_for_rails_env_on_lhs(node)
|
||||
else
|
||||
build_predicate_method_for_rails_env_on_rhs(node)
|
||||
end
|
||||
end
|
||||
|
||||
def rails_env_on_lhs?(node)
|
||||
comparing_str_env_with_rails_env_on_lhs?(node) ||
|
||||
comparing_sym_env_with_rails_env_on_lhs?(node)
|
||||
end
|
||||
|
||||
def build_predicate_method_for_rails_env_on_lhs(node)
|
||||
bang = node.method?(:!=) ? '!' : ''
|
||||
|
||||
"#{bang}#{node.receiver.source}.#{content(node.first_argument)}?"
|
||||
end
|
||||
|
||||
def build_predicate_method_for_rails_env_on_rhs(node)
|
||||
bang = node.method?(:!=) ? '!' : ''
|
||||
|
||||
"#{bang}#{node.first_argument.source}.#{content(node.receiver)}?"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
67
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/exit.rb
vendored
Normal file
67
Library/Homebrew/vendor/bundle/ruby/2.6.0/gems/rubocop-rails-2.8.1/lib/rubocop/cop/rails/exit.rb
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop enforces that `exit` calls are not used within a rails app.
|
||||
# Valid options are instead to raise an error, break, return, or some
|
||||
# other form of stopping execution of current request.
|
||||
#
|
||||
# There are two obvious cases where `exit` is particularly harmful:
|
||||
#
|
||||
# * Usage in library code for your application. Even though Rails will
|
||||
# rescue from a `SystemExit` and continue on, unit testing that library
|
||||
# code will result in specs exiting (potentially silently if `exit(0)`
|
||||
# is used.)
|
||||
# * Usage in application code outside of the web process could result in
|
||||
# the program exiting, which could result in the code failing to run and
|
||||
# do its job.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# exit(0)
|
||||
#
|
||||
# # good
|
||||
# raise 'a bad error has happened'
|
||||
class Exit < Cop
|
||||
include ConfigurableEnforcedStyle
|
||||
|
||||
MSG = 'Do not use `exit` in Rails applications.'
|
||||
TARGET_METHODS = %i[exit exit!].freeze
|
||||
EXPLICIT_RECEIVERS = %i[Kernel Process].freeze
|
||||
|
||||
def on_send(node)
|
||||
add_offense(node, location: :selector) if offending_node?(node)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def offending_node?(node)
|
||||
right_method_name?(node.method_name) &&
|
||||
right_argument_count?(node.arguments) &&
|
||||
right_receiver?(node.receiver)
|
||||
end
|
||||
|
||||
def right_method_name?(method_name)
|
||||
TARGET_METHODS.include?(method_name)
|
||||
end
|
||||
|
||||
# More than 1 argument likely means it is a different
|
||||
# `exit` implementation than the one we are preventing.
|
||||
def right_argument_count?(arg_nodes)
|
||||
arg_nodes.size <= 1
|
||||
end
|
||||
|
||||
# Only register if exit is being called explicitly on `Kernel`,
|
||||
# `Process`, or if receiver node is nil for plain `exit` calls.
|
||||
def right_receiver?(receiver_node)
|
||||
return true unless receiver_node
|
||||
|
||||
_a, receiver_node_class, _c = *receiver_node
|
||||
|
||||
EXPLICIT_RECEIVERS.include?(receiver_node_class)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,109 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop is used to identify usages of file path joining process
|
||||
# to use `Rails.root.join` clause. It is used to add uniformity when
|
||||
# joining paths.
|
||||
#
|
||||
# @example EnforcedStyle: arguments
|
||||
# # bad
|
||||
# Rails.root.join('app/models/goober')
|
||||
# File.join(Rails.root, 'app/models/goober')
|
||||
# "#{Rails.root}/app/models/goober"
|
||||
#
|
||||
# # good
|
||||
# Rails.root.join('app', 'models', 'goober')
|
||||
#
|
||||
# @example EnforcedStyle: slashes (default)
|
||||
# # bad
|
||||
# Rails.root.join('app', 'models', 'goober')
|
||||
# File.join(Rails.root, 'app/models/goober')
|
||||
# "#{Rails.root}/app/models/goober"
|
||||
#
|
||||
# # good
|
||||
# Rails.root.join('app/models/goober')
|
||||
#
|
||||
class FilePath < Cop
|
||||
include ConfigurableEnforcedStyle
|
||||
include RangeHelp
|
||||
|
||||
MSG_SLASHES = 'Please use `Rails.root.join(\'path/to\')` ' \
|
||||
'instead.'
|
||||
MSG_ARGUMENTS = 'Please use `Rails.root.join(\'path\', \'to\')` ' \
|
||||
'instead.'
|
||||
|
||||
def_node_matcher :file_join_nodes?, <<~PATTERN
|
||||
(send (const nil? :File) :join ...)
|
||||
PATTERN
|
||||
|
||||
def_node_search :rails_root_nodes?, <<~PATTERN
|
||||
(send (const nil? :Rails) :root)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :rails_root_join_nodes?, <<~PATTERN
|
||||
(send (send (const nil? :Rails) :root) :join ...)
|
||||
PATTERN
|
||||
|
||||
def on_dstr(node)
|
||||
return unless rails_root_nodes?(node)
|
||||
return unless node.children.last.str_type?
|
||||
return unless node.children.last.source.start_with?('.') ||
|
||||
node.children.last.source.include?(File::SEPARATOR)
|
||||
|
||||
register_offense(node)
|
||||
end
|
||||
|
||||
def on_send(node)
|
||||
check_for_file_join_with_rails_root(node)
|
||||
check_for_rails_root_join_with_slash_separated_path(node)
|
||||
check_for_rails_root_join_with_string_arguments(node)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_for_file_join_with_rails_root(node)
|
||||
return unless file_join_nodes?(node)
|
||||
return unless node.arguments.any? { |e| rails_root_nodes?(e) }
|
||||
|
||||
register_offense(node)
|
||||
end
|
||||
|
||||
def check_for_rails_root_join_with_string_arguments(node)
|
||||
return unless style == :slashes
|
||||
return unless rails_root_nodes?(node)
|
||||
return unless rails_root_join_nodes?(node)
|
||||
return unless node.arguments.size > 1
|
||||
return unless node.arguments.all?(&:str_type?)
|
||||
|
||||
register_offense(node)
|
||||
end
|
||||
|
||||
def check_for_rails_root_join_with_slash_separated_path(node)
|
||||
return unless style == :arguments
|
||||
return unless rails_root_nodes?(node)
|
||||
return unless rails_root_join_nodes?(node)
|
||||
return unless node.arguments.any? { |arg| string_with_slash?(arg) }
|
||||
|
||||
register_offense(node)
|
||||
end
|
||||
|
||||
def string_with_slash?(node)
|
||||
node.str_type? && node.source.include?('/')
|
||||
end
|
||||
|
||||
def register_offense(node)
|
||||
line_range = node.loc.column...node.loc.last_column
|
||||
source_range = source_range(processed_source.buffer, node.first_line,
|
||||
line_range)
|
||||
add_offense(node, location: source_range)
|
||||
end
|
||||
|
||||
def message(_node)
|
||||
format(style == :arguments ? MSG_ARGUMENTS : MSG_SLASHES)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop is used to identify usages of `where.first` and
|
||||
# change them to use `find_by` instead.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# User.where(name: 'Bruce').first
|
||||
# User.where(name: 'Bruce').take
|
||||
#
|
||||
# # good
|
||||
# User.find_by(name: 'Bruce')
|
||||
class FindBy < Cop
|
||||
include RangeHelp
|
||||
|
||||
MSG = 'Use `find_by` instead of `where.%<method>s`.'
|
||||
TARGET_SELECTORS = %i[first take].freeze
|
||||
|
||||
def_node_matcher :where_first?, <<~PATTERN
|
||||
(send ({send csend} _ :where ...) {:first :take})
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
return unless where_first?(node)
|
||||
|
||||
range = range_between(node.receiver.loc.selector.begin_pos,
|
||||
node.loc.selector.end_pos)
|
||||
|
||||
add_offense(node, location: range,
|
||||
message: format(MSG, method: node.method_name))
|
||||
end
|
||||
alias on_csend on_send
|
||||
|
||||
def autocorrect(node)
|
||||
# Don't autocorrect where(...).first, because it can return different
|
||||
# results from find_by. (They order records differently, so the
|
||||
# 'first' record can be different.)
|
||||
return if node.method?(:first)
|
||||
|
||||
where_loc = node.receiver.loc.selector
|
||||
first_loc = range_between(node.loc.dot.begin_pos,
|
||||
node.loc.selector.end_pos)
|
||||
|
||||
lambda do |corrector|
|
||||
corrector.replace(where_loc, 'find_by')
|
||||
corrector.replace(first_loc, '')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop enforces that `ActiveRecord#find` is used instead of
|
||||
# `where.take!`, `find_by!`, and `find_by_id!` to retrieve a single record
|
||||
# by primary key when you expect it to be found.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# User.where(id: id).take!
|
||||
# User.find_by_id!(id)
|
||||
# User.find_by!(id: id)
|
||||
#
|
||||
# # good
|
||||
# User.find(id)
|
||||
#
|
||||
class FindById < Cop
|
||||
include RangeHelp
|
||||
|
||||
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
|
||||
|
||||
def_node_matcher :where_take?, <<~PATTERN
|
||||
(send
|
||||
$(send _ :where
|
||||
(hash
|
||||
(pair (sym :id) $_))) :take!)
|
||||
PATTERN
|
||||
|
||||
def_node_matcher :find_by?, <<~PATTERN
|
||||
{
|
||||
(send _ :find_by_id! $_)
|
||||
(send _ :find_by! (hash (pair (sym :id) $_)))
|
||||
}
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
where_take?(node) do |where, id_value|
|
||||
range = where_take_offense_range(node, where)
|
||||
|
||||
good_method = build_good_method(id_value)
|
||||
bad_method = build_where_take_bad_method(id_value)
|
||||
message = format(MSG, good_method: good_method, bad_method: bad_method)
|
||||
|
||||
add_offense(node, location: range, message: message)
|
||||
end
|
||||
|
||||
find_by?(node) do |id_value|
|
||||
range = find_by_offense_range(node)
|
||||
|
||||
good_method = build_good_method(id_value)
|
||||
bad_method = build_find_by_bad_method(node, id_value)
|
||||
message = format(MSG, good_method: good_method, bad_method: bad_method)
|
||||
|
||||
add_offense(node, location: range, message: message)
|
||||
end
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
if (matches = where_take?(node))
|
||||
where, id_value = *matches
|
||||
range = where_take_offense_range(node, where)
|
||||
elsif (id_value = find_by?(node))
|
||||
range = find_by_offense_range(node)
|
||||
end
|
||||
|
||||
lambda do |corrector|
|
||||
replacement = build_good_method(id_value)
|
||||
corrector.replace(range, replacement)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def where_take_offense_range(node, where)
|
||||
range_between(where.loc.selector.begin_pos, node.loc.expression.end_pos)
|
||||
end
|
||||
|
||||
def find_by_offense_range(node)
|
||||
range_between(node.loc.selector.begin_pos, node.loc.expression.end_pos)
|
||||
end
|
||||
|
||||
def build_good_method(id_value)
|
||||
"find(#{id_value.source})"
|
||||
end
|
||||
|
||||
def build_where_take_bad_method(id_value)
|
||||
"where(id: #{id_value.source}).take!"
|
||||
end
|
||||
|
||||
def build_find_by_bad_method(node, id_value)
|
||||
case node.method_name
|
||||
when :find_by_id!
|
||||
"find_by_id!(#{id_value.source})"
|
||||
when :find_by!
|
||||
"find_by!(id: #{id_value.source})"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Rails
|
||||
# This cop is used to identify usages of `all.each` and
|
||||
# change them to use `all.find_each` instead.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# User.all.each
|
||||
#
|
||||
# # good
|
||||
# User.all.find_each
|
||||
class FindEach < Cop
|
||||
MSG = 'Use `find_each` instead of `each`.'
|
||||
|
||||
SCOPE_METHODS = %i[
|
||||
all eager_load includes joins left_joins left_outer_joins not preload
|
||||
references unscoped where
|
||||
].freeze
|
||||
IGNORED_METHODS = %i[order limit select].freeze
|
||||
|
||||
def on_send(node)
|
||||
return unless node.receiver&.send_type? &&
|
||||
node.method?(:each)
|
||||
|
||||
return unless SCOPE_METHODS.include?(node.receiver.method_name)
|
||||
return if method_chain(node).any? { |m| ignored_by_find_each?(m) }
|
||||
|
||||
add_offense(node, location: :selector)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
->(corrector) { corrector.replace(node.loc.selector, 'find_each') }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def method_chain(node)
|
||||
node.each_node(:send).map(&:method_name)
|
||||
end
|
||||
|
||||
def ignored_by_find_each?(relation_method)
|
||||
# Active Record's #find_each ignores various extra parameters
|
||||
IGNORED_METHODS.include?(relation_method)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user