rack 2.0.9 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/{HISTORY.md → CHANGELOG.md} +220 -155
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +77 -117
- data/Rakefile +25 -18
- data/SPEC +3 -4
- data/bin/rackup +1 -0
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +3 -1
- data/example/protectedlobster.ru +2 -0
- data/lib/rack.rb +63 -60
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +2 -0
- data/lib/rack/auth/basic.rb +4 -1
- data/lib/rack/auth/digest/md5.rb +9 -7
- data/lib/rack/auth/digest/nonce.rb +6 -3
- data/lib/rack/auth/digest/params.rb +4 -2
- data/lib/rack/auth/digest/request.rb +2 -0
- data/lib/rack/body_proxy.rb +3 -6
- data/lib/rack/builder.rb +38 -15
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +29 -6
- data/lib/rack/common_logger.rb +9 -8
- data/lib/rack/conditional_get.rb +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +3 -1
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +28 -17
- data/lib/rack/directory.rb +17 -14
- data/lib/rack/etag.rb +3 -1
- data/lib/rack/events.rb +5 -3
- data/lib/rack/file.rb +5 -173
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler.rb +7 -2
- data/lib/rack/handler/cgi.rb +3 -1
- data/lib/rack/handler/fastcgi.rb +4 -2
- data/lib/rack/handler/lsws.rb +3 -1
- data/lib/rack/handler/scgi.rb +9 -6
- data/lib/rack/handler/thin.rb +3 -1
- data/lib/rack/handler/webrick.rb +4 -2
- data/lib/rack/head.rb +2 -0
- data/lib/rack/lint.rb +14 -11
- data/lib/rack/lobster.rb +7 -5
- data/lib/rack/lock.rb +2 -0
- data/lib/rack/logger.rb +2 -0
- data/lib/rack/media_type.rb +10 -5
- data/lib/rack/method_override.rb +4 -2
- data/lib/rack/mime.rb +9 -1
- data/lib/rack/mock.rb +74 -15
- data/lib/rack/multipart.rb +5 -3
- data/lib/rack/multipart/generator.rb +6 -7
- data/lib/rack/multipart/parser.rb +51 -45
- data/lib/rack/multipart/uploaded_file.rb +2 -0
- data/lib/rack/null_logger.rb +2 -0
- data/lib/rack/query_parser.rb +51 -25
- data/lib/rack/recursive.rb +7 -5
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +79 -26
- data/lib/rack/response.rb +71 -31
- data/lib/rack/rewindable_input.rb +4 -2
- data/lib/rack/runtime.rb +4 -2
- data/lib/rack/sendfile.rb +15 -8
- data/lib/rack/server.rb +88 -16
- data/lib/rack/session/abstract/id.rb +40 -22
- data/lib/rack/session/cookie.rb +10 -9
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +4 -2
- data/lib/rack/show_exceptions.rb +15 -9
- data/lib/rack/show_status.rb +4 -2
- data/lib/rack/static.rb +15 -10
- data/lib/rack/tempfile_reaper.rb +2 -0
- data/lib/rack/urlmap.rb +11 -2
- data/lib/rack/utils.rb +54 -71
- data/rack.gemspec +17 -7
- metadata +30 -172
- data/test/builder/an_underscore_app.rb +0 -5
- data/test/builder/anything.rb +0 -5
- data/test/builder/comment.ru +0 -4
- data/test/builder/end.ru +0 -5
- data/test/builder/line.ru +0 -1
- data/test/builder/options.ru +0 -2
- data/test/cgi/assets/folder/test.js +0 -1
- data/test/cgi/assets/fonts/font.eot +0 -1
- data/test/cgi/assets/images/image.png +0 -1
- data/test/cgi/assets/index.html +0 -1
- data/test/cgi/assets/javascripts/app.js +0 -1
- data/test/cgi/assets/stylesheets/app.css +0 -1
- data/test/cgi/lighttpd.conf +0 -26
- data/test/cgi/rackup_stub.rb +0 -6
- data/test/cgi/sample_rackup.ru +0 -5
- data/test/cgi/test +0 -9
- data/test/cgi/test+directory/test+file +0 -1
- data/test/cgi/test.fcgi +0 -9
- data/test/cgi/test.gz +0 -0
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- data/test/helper.rb +0 -34
- data/test/multipart/bad_robots +0 -259
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +0 -6
- data/test/multipart/empty +0 -10
- data/test/multipart/fail_16384_nofile +0 -814
- data/test/multipart/file1.txt +0 -1
- data/test/multipart/filename_and_modification_param +0 -7
- data/test/multipart/filename_and_no_name +0 -6
- data/test/multipart/filename_with_encoded_words +0 -7
- data/test/multipart/filename_with_escaped_quotes +0 -6
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +0 -7
- data/test/multipart/filename_with_null_byte +0 -7
- data/test/multipart/filename_with_percent_escaped_quotes +0 -6
- data/test/multipart/filename_with_single_quote +0 -7
- data/test/multipart/filename_with_unescaped_percentages +0 -6
- data/test/multipart/filename_with_unescaped_percentages2 +0 -6
- data/test/multipart/filename_with_unescaped_percentages3 +0 -6
- data/test/multipart/filename_with_unescaped_quotes +0 -6
- data/test/multipart/ie +0 -6
- data/test/multipart/invalid_character +0 -6
- data/test/multipart/mixed_files +0 -21
- data/test/multipart/nested +0 -10
- data/test/multipart/none +0 -9
- data/test/multipart/quoted +0 -15
- data/test/multipart/rack-logo.png +0 -0
- data/test/multipart/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- data/test/multipart/unity3d_wwwform +0 -11
- data/test/multipart/webkit +0 -32
- data/test/rackup/config.ru +0 -31
- data/test/registering_handler/rack/handler/registering_myself.rb +0 -8
- data/test/spec_auth_basic.rb +0 -89
- data/test/spec_auth_digest.rb +0 -260
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -233
- data/test/spec_cascade.rb +0 -63
- data/test/spec_cgi.rb +0 -84
- data/test/spec_chunked.rb +0 -103
- data/test/spec_common_logger.rb +0 -95
- data/test/spec_conditional_get.rb +0 -103
- data/test/spec_config.rb +0 -23
- data/test/spec_content_length.rb +0 -86
- data/test/spec_content_type.rb +0 -46
- data/test/spec_deflater.rb +0 -375
- data/test/spec_directory.rb +0 -148
- data/test/spec_etag.rb +0 -108
- data/test/spec_events.rb +0 -133
- data/test/spec_fastcgi.rb +0 -85
- data/test/spec_file.rb +0 -264
- data/test/spec_handler.rb +0 -57
- data/test/spec_head.rb +0 -46
- data/test/spec_lint.rb +0 -515
- data/test/spec_lobster.rb +0 -59
- data/test/spec_lock.rb +0 -204
- data/test/spec_logger.rb +0 -24
- data/test/spec_media_type.rb +0 -42
- data/test/spec_method_override.rb +0 -110
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -359
- data/test/spec_multipart.rb +0 -722
- data/test/spec_null_logger.rb +0 -21
- data/test/spec_recursive.rb +0 -75
- data/test/spec_request.rb +0 -1407
- data/test/spec_response.rb +0 -528
- data/test/spec_rewindable_input.rb +0 -128
- data/test/spec_runtime.rb +0 -50
- data/test/spec_sendfile.rb +0 -125
- data/test/spec_server.rb +0 -193
- data/test/spec_session_abstract_id.rb +0 -31
- data/test/spec_session_abstract_session_hash.rb +0 -45
- data/test/spec_session_cookie.rb +0 -442
- data/test/spec_session_memcache.rb +0 -357
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_session_pool.rb +0 -247
- data/test/spec_show_exceptions.rb +0 -93
- data/test/spec_show_status.rb +0 -104
- data/test/spec_static.rb +0 -184
- data/test/spec_tempfile_reaper.rb +0 -64
- data/test/spec_thin.rb +0 -96
- data/test/spec_urlmap.rb +0 -237
- data/test/spec_utils.rb +0 -742
- data/test/spec_version.rb +0 -11
- data/test/spec_webrick.rb +0 -206
- data/test/static/another/index.html +0 -1
- data/test/static/foo.html +0 -1
- data/test/static/index.html +0 -1
- data/test/testrequest.rb +0 -78
- data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -1,99 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'rack/session/
|
4
|
-
require 'memcache'
|
3
|
+
require 'rack/session/dalli'
|
5
4
|
|
6
5
|
module Rack
|
7
6
|
module Session
|
8
|
-
|
9
|
-
|
10
|
-
# maintained in the cookie.
|
11
|
-
# You may treat Session::Memcache as you would Session::Pool with the
|
12
|
-
# following caveats.
|
13
|
-
#
|
14
|
-
# * Setting :expire_after to 0 would note to the Memcache server to hang
|
15
|
-
# onto the session data until it would drop it according to it's own
|
16
|
-
# specifications. However, the cookie sent to the client would expire
|
17
|
-
# immediately.
|
18
|
-
#
|
19
|
-
# Note that memcache does drop data before it may be listed to expire. For
|
20
|
-
# a full description of behaviour, please see memcache's documentation.
|
21
|
-
|
22
|
-
class Memcache < Abstract::PersistedSecure
|
23
|
-
attr_reader :mutex, :pool
|
24
|
-
|
25
|
-
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
|
26
|
-
:namespace => 'rack:session',
|
27
|
-
:memcache_server => 'localhost:11211'
|
28
|
-
|
29
|
-
def initialize(app, options={})
|
30
|
-
super
|
31
|
-
|
32
|
-
@mutex = Mutex.new
|
33
|
-
mserv = @default_options[:memcache_server]
|
34
|
-
mopts = @default_options.reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
|
35
|
-
|
36
|
-
@pool = options[:cache] || MemCache.new(mserv, mopts)
|
37
|
-
unless @pool.active? and @pool.servers.any?(&:alive?)
|
38
|
-
raise 'No memcache servers'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def generate_sid
|
43
|
-
loop do
|
44
|
-
sid = super
|
45
|
-
break sid unless @pool.get(sid.private_id, true)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def find_session(req, sid)
|
50
|
-
with_lock(req) do
|
51
|
-
unless sid and session = get_session_with_fallback(sid)
|
52
|
-
sid, session = generate_sid, {}
|
53
|
-
unless /^STORED/ =~ @pool.add(sid.private_id, session)
|
54
|
-
raise "Session collision on '#{sid.inspect}'"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
[sid, session]
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def write_session(req, session_id, new_session, options)
|
62
|
-
expiry = options[:expire_after]
|
63
|
-
expiry = expiry.nil? ? 0 : expiry + 1
|
64
|
-
|
65
|
-
with_lock(req) do
|
66
|
-
@pool.set session_id.private_id, new_session, expiry
|
67
|
-
session_id
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def delete_session(req, session_id, options)
|
72
|
-
with_lock(req) do
|
73
|
-
@pool.delete(session_id.public_id)
|
74
|
-
@pool.delete(session_id.private_id)
|
75
|
-
generate_sid unless options[:drop]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def with_lock(req)
|
80
|
-
@mutex.lock if req.multithread?
|
81
|
-
yield
|
82
|
-
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
83
|
-
if $VERBOSE
|
84
|
-
warn "#{self} is unable to find memcached server."
|
85
|
-
warn $!.inspect
|
86
|
-
end
|
87
|
-
raise
|
88
|
-
ensure
|
89
|
-
@mutex.unlock if @mutex.locked?
|
90
|
-
end
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
def get_session_with_fallback(sid)
|
95
|
-
@pool.get(sid.private_id) || @pool.get(sid.public_id)
|
96
|
-
end
|
97
|
-
end
|
7
|
+
warn "Rack::Session::Memcache is deprecated, please use Rack::Session::Dalli from 'dalli' gem instead."
|
8
|
+
Memcache = Dalli
|
98
9
|
end
|
99
10
|
end
|
data/lib/rack/session/pool.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
2
4
|
# THANKS:
|
3
5
|
# apeiros, for session id generation, expiry setup, and threadiness
|
@@ -26,9 +28,9 @@ module Rack
|
|
26
28
|
|
27
29
|
class Pool < Abstract::PersistedSecure
|
28
30
|
attr_reader :mutex, :pool
|
29
|
-
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :
|
31
|
+
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge drop: false
|
30
32
|
|
31
|
-
def initialize(app, options={})
|
33
|
+
def initialize(app, options = {})
|
32
34
|
super
|
33
35
|
@pool = Hash.new
|
34
36
|
@mutex = Mutex.new
|
data/lib/rack/show_exceptions.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'ostruct'
|
2
4
|
require 'erb'
|
3
5
|
require 'rack/request'
|
@@ -55,7 +57,7 @@ module Rack
|
|
55
57
|
private :accepts_html?
|
56
58
|
|
57
59
|
def dump_exception(exception)
|
58
|
-
string = "#{exception.class}: #{exception.message}\n"
|
60
|
+
string = "#{exception.class}: #{exception.message}\n".dup
|
59
61
|
string << exception.backtrace.map { |l| "\t#{l}" }.join("\n")
|
60
62
|
string
|
61
63
|
end
|
@@ -77,13 +79,13 @@ module Rack
|
|
77
79
|
frame.function = $4
|
78
80
|
|
79
81
|
begin
|
80
|
-
lineno = frame.lineno-1
|
82
|
+
lineno = frame.lineno - 1
|
81
83
|
lines = ::File.readlines(frame.filename)
|
82
|
-
frame.pre_context_lineno = [lineno-CONTEXT, 0].max
|
84
|
+
frame.pre_context_lineno = [lineno - CONTEXT, 0].max
|
83
85
|
frame.pre_context = lines[frame.pre_context_lineno...lineno]
|
84
86
|
frame.context_line = lines[lineno].chomp
|
85
|
-
frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
|
86
|
-
frame.post_context = lines[lineno+1..frame.post_context_lineno]
|
87
|
+
frame.post_context_lineno = [lineno + CONTEXT, lines.size].min
|
88
|
+
frame.post_context = lines[lineno + 1..frame.post_context_lineno]
|
87
89
|
rescue
|
88
90
|
end
|
89
91
|
|
@@ -93,7 +95,11 @@ module Rack
|
|
93
95
|
end
|
94
96
|
}.compact
|
95
97
|
|
96
|
-
|
98
|
+
template.result(binding)
|
99
|
+
end
|
100
|
+
|
101
|
+
def template
|
102
|
+
TEMPLATE
|
97
103
|
end
|
98
104
|
|
99
105
|
def h(obj) # :nodoc:
|
@@ -107,8 +113,8 @@ module Rack
|
|
107
113
|
|
108
114
|
# :stopdoc:
|
109
115
|
|
110
|
-
# adapted from Django <djangoproject.com>
|
111
|
-
# Copyright (c)
|
116
|
+
# adapted from Django <www.djangoproject.com>
|
117
|
+
# Copyright (c) Django Software Foundation and individual contributors.
|
112
118
|
# Used under the modified BSD license:
|
113
119
|
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
114
120
|
TEMPLATE = ERB.new(<<-'HTML'.gsub(/^ /, ''))
|
@@ -363,7 +369,7 @@ module Rack
|
|
363
369
|
<% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
|
364
370
|
<tr>
|
365
371
|
<td><%=h key %></td>
|
366
|
-
<td class="code"><div><%=h val %></div></td>
|
372
|
+
<td class="code"><div><%=h val.inspect %></div></td>
|
367
373
|
</tr>
|
368
374
|
<% } %>
|
369
375
|
</tbody>
|
data/lib/rack/show_status.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'erb'
|
2
4
|
require 'rack/request'
|
3
5
|
require 'rack/utils'
|
@@ -52,8 +54,8 @@ module Rack
|
|
52
54
|
|
53
55
|
# :stopdoc:
|
54
56
|
|
55
|
-
# adapted from Django <djangoproject.com>
|
56
|
-
# Copyright (c)
|
57
|
+
# adapted from Django <www.djangoproject.com>
|
58
|
+
# Copyright (c) Django Software Foundation and individual contributors.
|
57
59
|
# Used under the modified BSD license:
|
58
60
|
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
59
61
|
TEMPLATE = <<'HTML'
|
data/lib/rack/static.rb
CHANGED
@@ -1,11 +1,15 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/files"
|
2
4
|
require "rack/utils"
|
3
5
|
|
6
|
+
require_relative 'core_ext/regexp'
|
7
|
+
|
4
8
|
module Rack
|
5
9
|
|
6
10
|
# The Rack::Static middleware intercepts requests for static files
|
7
11
|
# (javascript files, images, stylesheets, etc) based on the url prefixes or
|
8
|
-
# route mappings passed in the options, and serves them using a Rack::
|
12
|
+
# route mappings passed in the options, and serves them using a Rack::Files
|
9
13
|
# object. This allows a Rack stack to serve both static and dynamic content.
|
10
14
|
#
|
11
15
|
# Examples:
|
@@ -82,8 +86,9 @@ module Rack
|
|
82
86
|
# ]
|
83
87
|
#
|
84
88
|
class Static
|
89
|
+
using ::Rack::RegexpExtensions
|
85
90
|
|
86
|
-
def initialize(app, options={})
|
91
|
+
def initialize(app, options = {})
|
87
92
|
@app = app
|
88
93
|
@urls = options[:urls] || ["/favicon.ico"]
|
89
94
|
@index = options[:index]
|
@@ -93,13 +98,13 @@ module Rack
|
|
93
98
|
# HTTP Headers
|
94
99
|
@header_rules = options[:header_rules] || []
|
95
100
|
# Allow for legacy :cache_control option while prioritizing global header_rules setting
|
96
|
-
@header_rules.unshift([:all, {CACHE_CONTROL => options[:cache_control]}]) if options[:cache_control]
|
101
|
+
@header_rules.unshift([:all, { CACHE_CONTROL => options[:cache_control] }]) if options[:cache_control]
|
97
102
|
|
98
|
-
@file_server = Rack::
|
103
|
+
@file_server = Rack::Files.new(root)
|
99
104
|
end
|
100
105
|
|
101
106
|
def add_index_root?(path)
|
102
|
-
@index && path
|
107
|
+
@index && route_file(path) && path.end_with?('/')
|
103
108
|
end
|
104
109
|
|
105
110
|
def overwrite_file_path(path)
|
@@ -120,7 +125,7 @@ module Rack
|
|
120
125
|
if can_serve(path)
|
121
126
|
if overwrite_file_path(path)
|
122
127
|
env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
|
123
|
-
elsif @gzip && env['HTTP_ACCEPT_ENCODING']
|
128
|
+
elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
|
124
129
|
path = env[PATH_INFO]
|
125
130
|
env[PATH_INFO] += '.gz'
|
126
131
|
response = @file_server.call(env)
|
@@ -157,14 +162,14 @@ module Rack
|
|
157
162
|
when :all
|
158
163
|
true
|
159
164
|
when :fonts
|
160
|
-
|
165
|
+
/\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
|
161
166
|
when String
|
162
167
|
path = ::Rack::Utils.unescape(path)
|
163
168
|
path.start_with?(rule) || path.start_with?('/' + rule)
|
164
169
|
when Array
|
165
|
-
|
170
|
+
/\.(#{rule.join('|')})\z/.match?(path)
|
166
171
|
when Regexp
|
167
|
-
path
|
172
|
+
rule.match?(path)
|
168
173
|
else
|
169
174
|
false
|
170
175
|
end
|
data/lib/rack/tempfile_reaper.rb
CHANGED
data/lib/rack/urlmap.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
1
5
|
module Rack
|
2
6
|
# Rack::URLMap takes a hash mapping urls or paths to apps, and
|
3
7
|
# dispatches accordingly. Support for HTTP/1.1 host names exists if
|
@@ -20,9 +24,11 @@ module Rack
|
|
20
24
|
end
|
21
25
|
|
22
26
|
def remap(map)
|
27
|
+
@known_hosts = Set[]
|
23
28
|
@mapping = map.map { |location, app|
|
24
29
|
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
25
30
|
host, location = $1, $2
|
31
|
+
@known_hosts << host
|
26
32
|
else
|
27
33
|
host = nil
|
28
34
|
end
|
@@ -50,10 +56,13 @@ module Rack
|
|
50
56
|
is_same_server = casecmp?(http_host, server_name) ||
|
51
57
|
casecmp?(http_host, "#{server_name}:#{server_port}")
|
52
58
|
|
59
|
+
is_host_known = @known_hosts.include? http_host
|
60
|
+
|
53
61
|
@mapping.each do |host, location, match, app|
|
54
62
|
unless casecmp?(http_host, host) \
|
55
63
|
|| casecmp?(server_name, host) \
|
56
|
-
|| (!host && is_same_server)
|
64
|
+
|| (!host && is_same_server) \
|
65
|
+
|| (!host && !is_host_known) # If we don't have a matching host, default to the first without a specified host
|
57
66
|
next
|
58
67
|
end
|
59
68
|
|
@@ -68,7 +77,7 @@ module Rack
|
|
68
77
|
return app.call(env)
|
69
78
|
end
|
70
79
|
|
71
|
-
[404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
|
80
|
+
[404, { CONTENT_TYPE => "text/plain", "X-Cascade" => "pass" }, ["Not Found: #{path}"]]
|
72
81
|
|
73
82
|
ensure
|
74
83
|
env[PATH_INFO] = path
|
data/lib/rack/utils.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
require 'uri'
|
3
5
|
require 'fileutils'
|
4
6
|
require 'set'
|
@@ -6,11 +8,15 @@ require 'tempfile'
|
|
6
8
|
require 'rack/query_parser'
|
7
9
|
require 'time'
|
8
10
|
|
11
|
+
require_relative 'core_ext/regexp'
|
12
|
+
|
9
13
|
module Rack
|
10
14
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
11
15
|
# applications adopted from all kinds of Ruby libraries.
|
12
16
|
|
13
17
|
module Utils
|
18
|
+
using ::Rack::RegexpExtensions
|
19
|
+
|
14
20
|
ParameterTypeError = QueryParser::ParameterTypeError
|
15
21
|
InvalidParameterError = QueryParser::InvalidParameterError
|
16
22
|
DEFAULT_SEP = QueryParser::DEFAULT_SEP
|
@@ -118,7 +124,7 @@ module Rack
|
|
118
124
|
when Hash
|
119
125
|
value.map { |k, v|
|
120
126
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
121
|
-
}.
|
127
|
+
}.delete_if(&:empty?).join('&')
|
122
128
|
when nil
|
123
129
|
prefix
|
124
130
|
else
|
@@ -132,7 +138,7 @@ module Rack
|
|
132
138
|
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
133
139
|
value, parameters = part.split(/\s*;\s*/, 2)
|
134
140
|
quality = 1.0
|
135
|
-
if md = /\Aq=([\d.]+)/.match(parameters)
|
141
|
+
if parameters && (md = /\Aq=([\d.]+)/.match(parameters))
|
136
142
|
quality = md[1].to_f
|
137
143
|
end
|
138
144
|
[value, quality]
|
@@ -175,27 +181,26 @@ module Rack
|
|
175
181
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
176
182
|
|
177
183
|
expanded_accept_encoding =
|
178
|
-
accept_encoding.
|
184
|
+
accept_encoding.each_with_object([]) do |(m, q), list|
|
179
185
|
if m == "*"
|
180
|
-
(available_encodings - accept_encoding.map
|
186
|
+
(available_encodings - accept_encoding.map(&:first))
|
187
|
+
.each { |m2| list << [m2, q] }
|
181
188
|
else
|
182
|
-
[
|
189
|
+
list << [m, q]
|
183
190
|
end
|
184
|
-
|
185
|
-
mem + list
|
186
|
-
}
|
191
|
+
end
|
187
192
|
|
188
|
-
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map
|
193
|
+
encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map!(&:first)
|
189
194
|
|
190
195
|
unless encoding_candidates.include?("identity")
|
191
196
|
encoding_candidates.push("identity")
|
192
197
|
end
|
193
198
|
|
194
|
-
expanded_accept_encoding.each
|
199
|
+
expanded_accept_encoding.each do |m, q|
|
195
200
|
encoding_candidates.delete(m) if q == 0.0
|
196
|
-
|
201
|
+
end
|
197
202
|
|
198
|
-
|
203
|
+
(encoding_candidates & available_encodings)[0]
|
199
204
|
end
|
200
205
|
module_function :select_best_encoding
|
201
206
|
|
@@ -211,7 +216,7 @@ module Rack
|
|
211
216
|
# precede those with less specific. Ordering with respect to other
|
212
217
|
# attributes (e.g., Domain) is unspecified.
|
213
218
|
cookies = parse_query(header, ';,') { |s| unescape(s) rescue s }
|
214
|
-
cookies.each_with_object({}) { |(k,v), hash| hash[k] = Array === v ? v.first : v }
|
219
|
+
cookies.each_with_object({}) { |(k, v), hash| hash[k] = Array === v ? v.first : v }
|
215
220
|
end
|
216
221
|
module_function :parse_cookies_header
|
217
222
|
|
@@ -221,31 +226,7 @@ module Rack
|
|
221
226
|
domain = "; domain=#{value[:domain]}" if value[:domain]
|
222
227
|
path = "; path=#{value[:path]}" if value[:path]
|
223
228
|
max_age = "; max-age=#{value[:max_age]}" if value[:max_age]
|
224
|
-
|
225
|
-
# only are there contradicting RFCs and examples within RFC text, but
|
226
|
-
# there are also numerous conflicting names of fields and partially
|
227
|
-
# cross-applicable specifications.
|
228
|
-
#
|
229
|
-
# These are best described in RFC 2616 3.3.1. This RFC text also
|
230
|
-
# specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
|
231
|
-
# fixed length format with space-date delimited fields.
|
232
|
-
#
|
233
|
-
# See also RFC 1123 section 5.2.14.
|
234
|
-
#
|
235
|
-
# RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
|
236
|
-
# in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
|
237
|
-
# the space delimited format. These formats are compliant with RFC 2822.
|
238
|
-
#
|
239
|
-
# For reference, all involved RFCs are:
|
240
|
-
# RFC 822
|
241
|
-
# RFC 1123
|
242
|
-
# RFC 2109
|
243
|
-
# RFC 2616
|
244
|
-
# RFC 2822
|
245
|
-
# RFC 2965
|
246
|
-
# RFC 6265
|
247
|
-
expires = "; expires=" +
|
248
|
-
rfc2822(value[:expires].clone.gmtime) if value[:expires]
|
229
|
+
expires = "; expires=#{value[:expires].httpdate}" if value[:expires]
|
249
230
|
secure = "; secure" if value[:secure]
|
250
231
|
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
251
232
|
same_site =
|
@@ -253,11 +234,11 @@ module Rack
|
|
253
234
|
when false, nil
|
254
235
|
nil
|
255
236
|
when :none, 'None', :None
|
256
|
-
'; SameSite=None'
|
237
|
+
'; SameSite=None'
|
257
238
|
when :lax, 'Lax', :Lax
|
258
|
-
'; SameSite=Lax'
|
239
|
+
'; SameSite=Lax'
|
259
240
|
when true, :strict, 'Strict', :Strict
|
260
|
-
'; SameSite=Strict'
|
241
|
+
'; SameSite=Strict'
|
261
242
|
else
|
262
243
|
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
263
244
|
end
|
@@ -297,15 +278,15 @@ module Rack
|
|
297
278
|
cookies = header
|
298
279
|
end
|
299
280
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
}
|
281
|
+
regexp = if value[:domain]
|
282
|
+
/\A#{escape(key)}=.*domain=#{value[:domain]}/
|
283
|
+
elsif value[:path]
|
284
|
+
/\A#{escape(key)}=.*path=#{value[:path]}/
|
285
|
+
else
|
286
|
+
/\A#{escape(key)}=/
|
287
|
+
end
|
288
|
+
|
289
|
+
cookies.reject! { |cookie| regexp.match? cookie }
|
309
290
|
|
310
291
|
cookies.join("\n")
|
311
292
|
end
|
@@ -323,9 +304,9 @@ module Rack
|
|
323
304
|
new_header = make_delete_cookie_header(header, key, value)
|
324
305
|
|
325
306
|
add_cookie_to_header(new_header, key,
|
326
|
-
{:
|
327
|
-
:
|
328
|
-
:
|
307
|
+
{ value: '', path: nil, domain: nil,
|
308
|
+
max_age: '0',
|
309
|
+
expires: Time.at(0) }.merge(value))
|
329
310
|
|
330
311
|
end
|
331
312
|
module_function :add_remove_cookie_to_header
|
@@ -366,7 +347,7 @@ module Rack
|
|
366
347
|
ranges = []
|
367
348
|
$1.split(/,\s*/).each do |range_spec|
|
368
349
|
return nil unless range_spec =~ /(\d*)-(\d*)/
|
369
|
-
r0,r1 = $1, $2
|
350
|
+
r0, r1 = $1, $2
|
370
351
|
if r0.empty?
|
371
352
|
return nil if r1.empty?
|
372
353
|
# suffix-byte-range-spec, represents trailing suffix of file
|
@@ -380,7 +361,7 @@ module Rack
|
|
380
361
|
else
|
381
362
|
r1 = r1.to_i
|
382
363
|
return nil if r1 < r0 # backwards range is syntactically invalid
|
383
|
-
r1 = size-1 if r1 >= size
|
364
|
+
r1 = size - 1 if r1 >= size
|
384
365
|
end
|
385
366
|
end
|
386
367
|
ranges << (r0..r1) if r0 <= r1
|
@@ -401,7 +382,7 @@ module Rack
|
|
401
382
|
l = a.unpack("C*")
|
402
383
|
|
403
384
|
r, i = 0, -1
|
404
|
-
b.each_byte { |v| r |= v ^ l[i+=1] }
|
385
|
+
b.each_byte { |v| r |= v ^ l[i += 1] }
|
405
386
|
r == 0
|
406
387
|
end
|
407
388
|
module_function :secure_compare
|
@@ -427,19 +408,17 @@ module Rack
|
|
427
408
|
self.class.new(@for, app)
|
428
409
|
end
|
429
410
|
|
430
|
-
def context(env, app
|
411
|
+
def context(env, app = @app)
|
431
412
|
recontext(app).call(env)
|
432
413
|
end
|
433
414
|
end
|
434
415
|
|
435
416
|
# A case-insensitive Hash that preserves the original case of a
|
436
417
|
# header when set.
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
def initialize(hash={})
|
418
|
+
#
|
419
|
+
# @api private
|
420
|
+
class HeaderHash < Hash # :nodoc:
|
421
|
+
def initialize(hash = {})
|
443
422
|
super()
|
444
423
|
@names = {}
|
445
424
|
hash.each { |k, v| self[k] = v }
|
@@ -459,7 +438,7 @@ module Rack
|
|
459
438
|
|
460
439
|
def to_hash
|
461
440
|
hash = {}
|
462
|
-
each { |k,v| hash[k] = v }
|
441
|
+
each { |k, v| hash[k] = v }
|
463
442
|
hash
|
464
443
|
end
|
465
444
|
|
@@ -512,13 +491,14 @@ module Rack
|
|
512
491
|
|
513
492
|
# Every standard HTTP code mapped to the appropriate message.
|
514
493
|
# Generated with:
|
515
|
-
#
|
516
|
-
#
|
517
|
-
#
|
494
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
495
|
+
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
496
|
+
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
518
497
|
HTTP_STATUS_CODES = {
|
519
498
|
100 => 'Continue',
|
520
499
|
101 => 'Switching Protocols',
|
521
500
|
102 => 'Processing',
|
501
|
+
103 => 'Early Hints',
|
522
502
|
200 => 'OK',
|
523
503
|
201 => 'Created',
|
524
504
|
202 => 'Accepted',
|
@@ -535,6 +515,7 @@ module Rack
|
|
535
515
|
303 => 'See Other',
|
536
516
|
304 => 'Not Modified',
|
537
517
|
305 => 'Use Proxy',
|
518
|
+
306 => '(Unused)',
|
538
519
|
307 => 'Temporary Redirect',
|
539
520
|
308 => 'Permanent Redirect',
|
540
521
|
400 => 'Bad Request',
|
@@ -559,6 +540,7 @@ module Rack
|
|
559
540
|
422 => 'Unprocessable Entity',
|
560
541
|
423 => 'Locked',
|
561
542
|
424 => 'Failed Dependency',
|
543
|
+
425 => 'Too Early',
|
562
544
|
426 => 'Upgrade Required',
|
563
545
|
428 => 'Precondition Required',
|
564
546
|
429 => 'Too Many Requests',
|
@@ -573,12 +555,13 @@ module Rack
|
|
573
555
|
506 => 'Variant Also Negotiates',
|
574
556
|
507 => 'Insufficient Storage',
|
575
557
|
508 => 'Loop Detected',
|
558
|
+
509 => 'Bandwidth Limit Exceeded',
|
576
559
|
510 => 'Not Extended',
|
577
560
|
511 => 'Network Authentication Required'
|
578
561
|
}
|
579
562
|
|
580
563
|
# Responses with HTTP status codes that should not have an entity body
|
581
|
-
STATUS_WITH_NO_ENTITY_BODY =
|
564
|
+
STATUS_WITH_NO_ENTITY_BODY = Hash[((100..199).to_a << 204 << 304).product([true])]
|
582
565
|
|
583
566
|
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
584
567
|
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
@@ -586,7 +569,7 @@ module Rack
|
|
586
569
|
|
587
570
|
def status_code(status)
|
588
571
|
if status.is_a?(Symbol)
|
589
|
-
SYMBOL_TO_STATUS_CODE
|
572
|
+
SYMBOL_TO_STATUS_CODE.fetch(status) { raise ArgumentError, "Unrecognized status code #{status.inspect}" }
|
590
573
|
else
|
591
574
|
status.to_i
|
592
575
|
end
|
@@ -607,11 +590,11 @@ module Rack
|
|
607
590
|
|
608
591
|
clean.unshift '/' if parts.empty? || parts.first.empty?
|
609
592
|
|
610
|
-
::File.join
|
593
|
+
::File.join clean
|
611
594
|
end
|
612
595
|
module_function :clean_path_info
|
613
596
|
|
614
|
-
NULL_BYTE = "\0"
|
597
|
+
NULL_BYTE = "\0"
|
615
598
|
|
616
599
|
def valid_path?(path)
|
617
600
|
path.valid_encoding? && !path.include?(NULL_BYTE)
|