rack 2.1.4.4 → 2.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +713 -10
- data/CONTRIBUTING.md +136 -0
- data/README.rdoc +109 -38
- data/Rakefile +14 -7
- data/{SPEC → SPEC.rdoc} +35 -6
- data/lib/rack/auth/abstract/request.rb +0 -2
- data/lib/rack/auth/basic.rb +4 -5
- data/lib/rack/auth/digest/md5.rb +4 -4
- data/lib/rack/auth/digest/nonce.rb +2 -3
- data/lib/rack/auth/digest/request.rb +3 -3
- data/lib/rack/body_proxy.rb +13 -9
- data/lib/rack/builder.rb +77 -8
- data/lib/rack/cascade.rb +23 -8
- data/lib/rack/chunked.rb +48 -23
- data/lib/rack/common_logger.rb +27 -19
- data/lib/rack/conditional_get.rb +18 -16
- data/lib/rack/content_length.rb +6 -7
- data/lib/rack/content_type.rb +3 -4
- data/lib/rack/deflater.rb +45 -35
- data/lib/rack/directory.rb +77 -60
- data/lib/rack/etag.rb +4 -3
- data/lib/rack/events.rb +15 -18
- data/lib/rack/file.rb +1 -1
- data/lib/rack/files.rb +96 -56
- data/lib/rack/handler/cgi.rb +1 -4
- data/lib/rack/handler/fastcgi.rb +1 -3
- data/lib/rack/handler/lsws.rb +1 -3
- data/lib/rack/handler/scgi.rb +1 -3
- data/lib/rack/handler/thin.rb +1 -3
- data/lib/rack/handler/webrick.rb +12 -5
- data/lib/rack/head.rb +0 -2
- data/lib/rack/lint.rb +211 -179
- data/lib/rack/lobster.rb +3 -5
- data/lib/rack/lock.rb +0 -1
- data/lib/rack/media_type.rb +17 -7
- data/lib/rack/method_override.rb +1 -1
- data/lib/rack/mock.rb +54 -7
- data/lib/rack/multipart/generator.rb +11 -6
- data/lib/rack/multipart/parser.rb +17 -17
- data/lib/rack/multipart/uploaded_file.rb +13 -7
- data/lib/rack/multipart.rb +1 -1
- data/lib/rack/query_parser.rb +62 -16
- data/lib/rack/recursive.rb +1 -1
- data/lib/rack/reloader.rb +1 -3
- data/lib/rack/request.rb +184 -78
- data/lib/rack/response.rb +62 -19
- data/lib/rack/rewindable_input.rb +0 -1
- data/lib/rack/runtime.rb +3 -3
- data/lib/rack/sendfile.rb +1 -4
- data/lib/rack/server.rb +9 -8
- data/lib/rack/session/abstract/id.rb +21 -18
- data/lib/rack/session/cookie.rb +4 -6
- data/lib/rack/session/pool.rb +7 -2
- data/lib/rack/show_exceptions.rb +6 -8
- data/lib/rack/show_status.rb +5 -7
- data/lib/rack/static.rb +15 -7
- data/lib/rack/tempfile_reaper.rb +0 -2
- data/lib/rack/urlmap.rb +2 -5
- data/lib/rack/utils.rb +69 -58
- data/lib/rack/version.rb +29 -0
- data/lib/rack.rb +7 -16
- data/rack.gemspec +31 -29
- metadata +12 -16
@@ -3,10 +3,8 @@
|
|
3
3
|
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
4
4
|
# bugrep: Andreas Zehnder
|
5
5
|
|
6
|
-
|
6
|
+
require_relative '../../../rack'
|
7
7
|
require 'time'
|
8
|
-
require 'rack/request'
|
9
|
-
require 'rack/response'
|
10
8
|
require 'securerandom'
|
11
9
|
require 'digest/sha2'
|
12
10
|
|
@@ -44,18 +42,6 @@ module Rack
|
|
44
42
|
# SessionHash is responsible to lazily load the session from store.
|
45
43
|
|
46
44
|
class SessionHash
|
47
|
-
using Module.new {
|
48
|
-
refine Hash do
|
49
|
-
def transform_keys(&block)
|
50
|
-
hash = {}
|
51
|
-
each do |key, value|
|
52
|
-
hash[block.call(key)] = value
|
53
|
-
end
|
54
|
-
hash
|
55
|
-
end
|
56
|
-
end
|
57
|
-
} unless {}.respond_to?(:transform_keys)
|
58
|
-
|
59
45
|
include Enumerable
|
60
46
|
attr_writer :id
|
61
47
|
|
@@ -98,6 +84,11 @@ module Rack
|
|
98
84
|
@data[key.to_s]
|
99
85
|
end
|
100
86
|
|
87
|
+
def dig(key, *keys)
|
88
|
+
load_for_read!
|
89
|
+
@data.dig(key.to_s, *keys)
|
90
|
+
end
|
91
|
+
|
101
92
|
def fetch(key, default = Unspecified, &block)
|
102
93
|
load_for_read!
|
103
94
|
if default == Unspecified
|
@@ -201,14 +192,19 @@ module Rack
|
|
201
192
|
end
|
202
193
|
|
203
194
|
def stringify_keys(other)
|
204
|
-
|
195
|
+
# Use transform_keys after dropping Ruby 2.4 support
|
196
|
+
hash = {}
|
197
|
+
other.to_hash.each do |key, value|
|
198
|
+
hash[key.to_s] = value
|
199
|
+
end
|
200
|
+
hash
|
205
201
|
end
|
206
202
|
end
|
207
203
|
|
208
204
|
# ID sets up a basic framework for implementing an id based sessioning
|
209
205
|
# service. Cookies sent to the client for maintaining sessions will only
|
210
|
-
# contain an id reference. Only #find_session
|
211
|
-
# required to be overwritten.
|
206
|
+
# contain an id reference. Only #find_session, #write_session and
|
207
|
+
# #delete_session are required to be overwritten.
|
212
208
|
#
|
213
209
|
# All parameters are optional.
|
214
210
|
# * :key determines the name of the cookie, by default it is
|
@@ -256,6 +252,7 @@ module Rack
|
|
256
252
|
@default_options = self.class::DEFAULT_OPTIONS.merge(options)
|
257
253
|
@key = @default_options.delete(:key)
|
258
254
|
@cookie_only = @default_options.delete(:cookie_only)
|
255
|
+
@same_site = @default_options.delete(:same_site)
|
259
256
|
initialize_sid
|
260
257
|
end
|
261
258
|
|
@@ -397,6 +394,12 @@ module Rack
|
|
397
394
|
cookie[:value] = cookie_value(data)
|
398
395
|
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
399
396
|
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
|
397
|
+
|
398
|
+
if @same_site.respond_to? :call
|
399
|
+
cookie[:same_site] = @same_site.call(req, res)
|
400
|
+
else
|
401
|
+
cookie[:same_site] = @same_site
|
402
|
+
end
|
400
403
|
set_cookie(req, res, cookie.merge!(options))
|
401
404
|
end
|
402
405
|
end
|
data/lib/rack/session/cookie.rb
CHANGED
@@ -2,11 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'zlib'
|
5
|
-
|
6
|
-
require 'rack/response'
|
7
|
-
require 'rack/session/abstract/id'
|
5
|
+
require_relative 'abstract/id'
|
8
6
|
require 'json'
|
9
|
-
require '
|
7
|
+
require 'delegate'
|
10
8
|
|
11
9
|
module Rack
|
12
10
|
|
@@ -52,11 +50,11 @@ module Rack
|
|
52
50
|
# Encode session cookies as Base64
|
53
51
|
class Base64
|
54
52
|
def encode(str)
|
55
|
-
|
53
|
+
[str].pack("m0")
|
56
54
|
end
|
57
55
|
|
58
56
|
def decode(str)
|
59
|
-
|
57
|
+
str.unpack("m").first
|
60
58
|
end
|
61
59
|
|
62
60
|
# Encode session cookies as Marshaled Base64 data
|
data/lib/rack/session/pool.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# apeiros, for session id generation, expiry setup, and threadiness
|
6
6
|
# sergio, threadiness and bugreps
|
7
7
|
|
8
|
-
|
8
|
+
require_relative 'abstract/id'
|
9
9
|
require 'thread'
|
10
10
|
|
11
11
|
module Rack
|
@@ -55,6 +55,7 @@ module Rack
|
|
55
55
|
|
56
56
|
def write_session(req, session_id, new_session, options)
|
57
57
|
with_lock(req) do
|
58
|
+
return false unless get_session_with_fallback(session_id)
|
58
59
|
@pool.store session_id.private_id, new_session
|
59
60
|
session_id
|
60
61
|
end
|
@@ -64,7 +65,11 @@ module Rack
|
|
64
65
|
with_lock(req) do
|
65
66
|
@pool.delete(session_id.public_id)
|
66
67
|
@pool.delete(session_id.private_id)
|
67
|
-
|
68
|
+
unless options[:drop]
|
69
|
+
sid = generate_sid
|
70
|
+
@pool.store(sid.private_id, {})
|
71
|
+
sid
|
72
|
+
end
|
68
73
|
end
|
69
74
|
end
|
70
75
|
|
data/lib/rack/show_exceptions.rb
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'ostruct'
|
4
4
|
require 'erb'
|
5
|
-
require 'rack/request'
|
6
|
-
require 'rack/utils'
|
7
5
|
|
8
6
|
module Rack
|
9
7
|
# Rack::ShowExceptions catches all exceptions raised from the app it
|
@@ -65,12 +63,12 @@ module Rack
|
|
65
63
|
def pretty(env, exception)
|
66
64
|
req = Rack::Request.new(env)
|
67
65
|
|
68
|
-
# This double assignment is to prevent an "unused variable" warning
|
69
|
-
#
|
66
|
+
# This double assignment is to prevent an "unused variable" warning.
|
67
|
+
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
70
68
|
path = path = (req.script_name + req.path_info).squeeze("/")
|
71
69
|
|
72
|
-
# This double assignment is to prevent an "unused variable" warning
|
73
|
-
#
|
70
|
+
# This double assignment is to prevent an "unused variable" warning.
|
71
|
+
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
74
72
|
frames = frames = exception.backtrace.map { |line|
|
75
73
|
frame = OpenStruct.new
|
76
74
|
if line =~ /(.*?):(\d+)(:in `(.*)')?/
|
@@ -313,7 +311,7 @@ module Rack
|
|
313
311
|
<% end %>
|
314
312
|
|
315
313
|
<h3 id="post-info">POST</h3>
|
316
|
-
<% if req.POST and not req.POST.empty? %>
|
314
|
+
<% if ((req.POST and not req.POST.empty?) rescue (no_post_data = "Invalid POST data"; nil)) %>
|
317
315
|
<table class="req">
|
318
316
|
<thead>
|
319
317
|
<tr>
|
@@ -331,7 +329,7 @@ module Rack
|
|
331
329
|
</tbody>
|
332
330
|
</table>
|
333
331
|
<% else %>
|
334
|
-
<p
|
332
|
+
<p><%= no_post_data || "No POST data" %>.</p>
|
335
333
|
<% end %>
|
336
334
|
|
337
335
|
|
data/lib/rack/show_status.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'erb'
|
4
|
-
require 'rack/request'
|
5
|
-
require 'rack/utils'
|
6
4
|
|
7
5
|
module Rack
|
8
6
|
# Rack::ShowStatus catches all empty responses and replaces them
|
@@ -20,19 +18,19 @@ module Rack
|
|
20
18
|
|
21
19
|
def call(env)
|
22
20
|
status, headers, body = @app.call(env)
|
23
|
-
headers = Utils::HeaderHash
|
21
|
+
headers = Utils::HeaderHash[headers]
|
24
22
|
empty = headers[CONTENT_LENGTH].to_i <= 0
|
25
23
|
|
26
24
|
# client or server error, or explicit message
|
27
25
|
if (status.to_i >= 400 && empty) || env[RACK_SHOWSTATUS_DETAIL]
|
28
|
-
# This double assignment is to prevent an "unused variable" warning
|
29
|
-
#
|
26
|
+
# This double assignment is to prevent an "unused variable" warning.
|
27
|
+
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
30
28
|
req = req = Rack::Request.new(env)
|
31
29
|
|
32
30
|
message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
|
33
31
|
|
34
|
-
# This double assignment is to prevent an "unused variable" warning
|
35
|
-
#
|
32
|
+
# This double assignment is to prevent an "unused variable" warning.
|
33
|
+
# Yes, it is dumb, but I don't like Ruby yelling at me.
|
36
34
|
detail = detail = env[RACK_SHOWSTATUS_DETAIL] || message
|
37
35
|
|
38
36
|
body = @template.result(binding)
|
data/lib/rack/static.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack/files"
|
4
|
-
require "rack/utils"
|
5
|
-
|
6
|
-
require_relative 'core_ext/regexp'
|
7
|
-
|
8
3
|
module Rack
|
9
4
|
|
10
5
|
# The Rack::Static middleware intercepts requests for static files
|
@@ -19,6 +14,11 @@ module Rack
|
|
19
14
|
#
|
20
15
|
# use Rack::Static, :urls => ["/media"]
|
21
16
|
#
|
17
|
+
# Same as previous, but instead of returning 404 for missing files under
|
18
|
+
# /media, call the next middleware:
|
19
|
+
#
|
20
|
+
# use Rack::Static, :urls => ["/media"], :cascade => true
|
21
|
+
#
|
22
22
|
# Serve all requests beginning with /css or /images from the folder "public"
|
23
23
|
# in the current directory (ie public/css/* and public/images/*):
|
24
24
|
#
|
@@ -86,13 +86,14 @@ module Rack
|
|
86
86
|
# ]
|
87
87
|
#
|
88
88
|
class Static
|
89
|
-
using ::Rack::RegexpExtensions
|
89
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
90
90
|
|
91
91
|
def initialize(app, options = {})
|
92
92
|
@app = app
|
93
93
|
@urls = options[:urls] || ["/favicon.ico"]
|
94
94
|
@index = options[:index]
|
95
95
|
@gzip = options[:gzip]
|
96
|
+
@cascade = options[:cascade]
|
96
97
|
root = options[:root] || Dir.pwd
|
97
98
|
|
98
99
|
# HTTP Headers
|
@@ -121,8 +122,9 @@ module Rack
|
|
121
122
|
|
122
123
|
def call(env)
|
123
124
|
path = env[PATH_INFO]
|
125
|
+
actual_path = Utils.clean_path_info(Utils.unescape_path(path))
|
124
126
|
|
125
|
-
if can_serve(
|
127
|
+
if can_serve(actual_path)
|
126
128
|
if overwrite_file_path(path)
|
127
129
|
env[PATH_INFO] = (add_index_root?(path) ? path + @index : @urls[path])
|
128
130
|
elsif @gzip && env['HTTP_ACCEPT_ENCODING'] && /\bgzip\b/.match?(env['HTTP_ACCEPT_ENCODING'])
|
@@ -133,6 +135,8 @@ module Rack
|
|
133
135
|
|
134
136
|
if response[0] == 404
|
135
137
|
response = nil
|
138
|
+
elsif response[0] == 304
|
139
|
+
# Do nothing, leave headers as is
|
136
140
|
else
|
137
141
|
if mime_type = Mime.mime_type(::File.extname(path), 'text/plain')
|
138
142
|
response[1][CONTENT_TYPE] = mime_type
|
@@ -144,6 +148,10 @@ module Rack
|
|
144
148
|
path = env[PATH_INFO]
|
145
149
|
response ||= @file_server.call(env)
|
146
150
|
|
151
|
+
if @cascade && response[0] == 404
|
152
|
+
return @app.call(env)
|
153
|
+
end
|
154
|
+
|
147
155
|
headers = response[1]
|
148
156
|
applicable_rules(path).each do |rule, new_headers|
|
149
157
|
new_headers.each { |field, content| headers[field] = content }
|
data/lib/rack/tempfile_reaper.rb
CHANGED
data/lib/rack/urlmap.rb
CHANGED
@@ -16,9 +16,6 @@ module Rack
|
|
16
16
|
# first, since they are most specific.
|
17
17
|
|
18
18
|
class URLMap
|
19
|
-
NEGATIVE_INFINITY = -1.0 / 0.0
|
20
|
-
INFINITY = 1.0 / 0.0
|
21
|
-
|
22
19
|
def initialize(map = {})
|
23
20
|
remap(map)
|
24
21
|
end
|
@@ -38,11 +35,11 @@ module Rack
|
|
38
35
|
end
|
39
36
|
|
40
37
|
location = location.chomp('/')
|
41
|
-
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)",
|
38
|
+
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
|
42
39
|
|
43
40
|
[host, location, match, app]
|
44
41
|
}.sort_by do |(host, location, _, _)|
|
45
|
-
[host ? -host.size : INFINITY, -location.size]
|
42
|
+
[host ? -host.size : Float::INFINITY, -location.size]
|
46
43
|
end
|
47
44
|
end
|
48
45
|
|
data/lib/rack/utils.rb
CHANGED
@@ -5,17 +5,16 @@ require 'uri'
|
|
5
5
|
require 'fileutils'
|
6
6
|
require 'set'
|
7
7
|
require 'tempfile'
|
8
|
-
require 'rack/query_parser'
|
9
8
|
require 'time'
|
10
9
|
|
11
|
-
require_relative '
|
10
|
+
require_relative 'query_parser'
|
12
11
|
|
13
12
|
module Rack
|
14
13
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
15
14
|
# applications adopted from all kinds of Ruby libraries.
|
16
15
|
|
17
16
|
module Utils
|
18
|
-
using ::Rack::RegexpExtensions
|
17
|
+
(require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
|
19
18
|
|
20
19
|
ParameterTypeError = QueryParser::ParameterTypeError
|
21
20
|
InvalidParameterError = QueryParser::InvalidParameterError
|
@@ -23,6 +22,10 @@ module Rack
|
|
23
22
|
COMMON_SEP = QueryParser::COMMON_SEP
|
24
23
|
KeySpaceConstrainedParams = QueryParser::Params
|
25
24
|
|
25
|
+
RFC2822_DAY_NAME = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
|
26
|
+
RFC2822_MONTH_NAME = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
|
27
|
+
RFC2396_PARSER = defined?(URI::RFC2396_PARSER) ? URI::RFC2396_PARSER : URI::RFC2396_Parser.new
|
28
|
+
|
26
29
|
class << self
|
27
30
|
attr_accessor :default_query_parser
|
28
31
|
end
|
@@ -30,33 +33,30 @@ module Rack
|
|
30
33
|
# This helps prevent a rogue client from flooding a Request.
|
31
34
|
self.default_query_parser = QueryParser.make_default(65536, 100)
|
32
35
|
|
36
|
+
module_function
|
37
|
+
|
33
38
|
# URI escapes. (CGI style space to +)
|
34
39
|
def escape(s)
|
35
40
|
URI.encode_www_form_component(s)
|
36
41
|
end
|
37
|
-
module_function :escape
|
38
42
|
|
39
43
|
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
40
44
|
# true URI escaping.
|
41
45
|
def escape_path(s)
|
42
|
-
|
46
|
+
RFC2396_PARSER.escape s
|
43
47
|
end
|
44
|
-
module_function :escape_path
|
45
48
|
|
46
49
|
# Unescapes the **path** component of a URI. See Rack::Utils.unescape for
|
47
50
|
# unescaping query parameters or form components.
|
48
51
|
def unescape_path(s)
|
49
|
-
|
52
|
+
RFC2396_PARSER.unescape s
|
50
53
|
end
|
51
|
-
module_function :unescape_path
|
52
|
-
|
53
54
|
|
54
55
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
55
56
|
# target encoding of the string returned, and it defaults to UTF-8
|
56
57
|
def unescape(s, encoding = Encoding::UTF_8)
|
57
58
|
URI.decode_www_form_component(s, encoding)
|
58
59
|
end
|
59
|
-
module_function :unescape
|
60
60
|
|
61
61
|
class << self
|
62
62
|
attr_accessor :multipart_total_part_limit
|
@@ -99,21 +99,20 @@ module Rack
|
|
99
99
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
100
100
|
end
|
101
101
|
else
|
102
|
+
# :nocov:
|
102
103
|
def clock_time
|
103
104
|
Time.now.to_f
|
104
105
|
end
|
106
|
+
# :nocov:
|
105
107
|
end
|
106
|
-
module_function :clock_time
|
107
108
|
|
108
109
|
def parse_query(qs, d = nil, &unescaper)
|
109
110
|
Rack::Utils.default_query_parser.parse_query(qs, d, &unescaper)
|
110
111
|
end
|
111
|
-
module_function :parse_query
|
112
112
|
|
113
113
|
def parse_nested_query(qs, d = nil)
|
114
114
|
Rack::Utils.default_query_parser.parse_nested_query(qs, d)
|
115
115
|
end
|
116
|
-
module_function :parse_nested_query
|
117
116
|
|
118
117
|
def build_query(params)
|
119
118
|
params.map { |k, v|
|
@@ -124,7 +123,6 @@ module Rack
|
|
124
123
|
end
|
125
124
|
}.join("&")
|
126
125
|
end
|
127
|
-
module_function :build_query
|
128
126
|
|
129
127
|
def build_nested_query(value, prefix = nil)
|
130
128
|
case value
|
@@ -143,7 +141,6 @@ module Rack
|
|
143
141
|
"#{prefix}=#{escape(value)}"
|
144
142
|
end
|
145
143
|
end
|
146
|
-
module_function :build_nested_query
|
147
144
|
|
148
145
|
def q_values(q_value_header)
|
149
146
|
q_value_header.to_s.split(',').map do |part|
|
@@ -155,8 +152,11 @@ module Rack
|
|
155
152
|
[value, quality]
|
156
153
|
end
|
157
154
|
end
|
158
|
-
module_function :q_values
|
159
155
|
|
156
|
+
# Return best accept value to use, based on the algorithm
|
157
|
+
# in RFC 2616 Section 14. If there are multiple best
|
158
|
+
# matches (same specificity and quality), the value returned
|
159
|
+
# is arbitrary.
|
160
160
|
def best_q_match(q_value_header, available_mimes)
|
161
161
|
values = q_values(q_value_header)
|
162
162
|
|
@@ -169,7 +169,6 @@ module Rack
|
|
169
169
|
end.last
|
170
170
|
matches && matches.first
|
171
171
|
end
|
172
|
-
module_function :best_q_match
|
173
172
|
|
174
173
|
ESCAPE_HTML = {
|
175
174
|
"&" => "&",
|
@@ -186,22 +185,27 @@ module Rack
|
|
186
185
|
def escape_html(string)
|
187
186
|
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
188
187
|
end
|
189
|
-
module_function :escape_html
|
190
188
|
|
191
189
|
def select_best_encoding(available_encodings, accept_encoding)
|
192
190
|
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
193
191
|
|
194
|
-
expanded_accept_encoding =
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
192
|
+
expanded_accept_encoding = []
|
193
|
+
|
194
|
+
accept_encoding.each do |m, q|
|
195
|
+
preference = available_encodings.index(m) || available_encodings.size
|
196
|
+
|
197
|
+
if m == "*"
|
198
|
+
(available_encodings - accept_encoding.map(&:first)).each do |m2|
|
199
|
+
expanded_accept_encoding << [m2, q, preference]
|
201
200
|
end
|
201
|
+
else
|
202
|
+
expanded_accept_encoding << [m, q, preference]
|
202
203
|
end
|
204
|
+
end
|
203
205
|
|
204
|
-
encoding_candidates = expanded_accept_encoding
|
206
|
+
encoding_candidates = expanded_accept_encoding
|
207
|
+
.sort_by { |_, q, p| [-q, p] }
|
208
|
+
.map!(&:first)
|
205
209
|
|
206
210
|
unless encoding_candidates.include?("identity")
|
207
211
|
encoding_candidates.push("identity")
|
@@ -213,27 +217,23 @@ module Rack
|
|
213
217
|
|
214
218
|
(encoding_candidates & available_encodings)[0]
|
215
219
|
end
|
216
|
-
module_function :select_best_encoding
|
217
220
|
|
218
221
|
def parse_cookies(env)
|
219
222
|
parse_cookies_header env[HTTP_COOKIE]
|
220
223
|
end
|
221
|
-
module_function :parse_cookies
|
222
224
|
|
223
225
|
def parse_cookies_header(header)
|
224
|
-
# According to RFC
|
225
|
-
#
|
226
|
-
#
|
227
|
-
#
|
228
|
-
# attributes (e.g., Domain) is unspecified.
|
226
|
+
# According to RFC 6265:
|
227
|
+
# The syntax for cookie headers only supports semicolons
|
228
|
+
# User Agent -> Server ==
|
229
|
+
# Cookie: SID=31d4d96e407aad42; lang=en-US
|
229
230
|
return {} unless header
|
230
|
-
header.split(/[
|
231
|
+
header.split(/[;] */n).each_with_object({}) do |cookie, cookies|
|
231
232
|
next if cookie.empty?
|
232
233
|
key, value = cookie.split('=', 2)
|
233
234
|
cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
|
234
235
|
end
|
235
236
|
end
|
236
|
-
module_function :parse_cookies_header
|
237
237
|
|
238
238
|
def add_cookie_to_header(header, key, value)
|
239
239
|
case value
|
@@ -275,13 +275,11 @@ module Rack
|
|
275
275
|
raise ArgumentError, "Unrecognized cookie header value. Expected String, Array, or nil, got #{header.inspect}"
|
276
276
|
end
|
277
277
|
end
|
278
|
-
module_function :add_cookie_to_header
|
279
278
|
|
280
279
|
def set_cookie_header!(header, key, value)
|
281
280
|
header[SET_COOKIE] = add_cookie_to_header(header[SET_COOKIE], key, value)
|
282
281
|
nil
|
283
282
|
end
|
284
|
-
module_function :set_cookie_header!
|
285
283
|
|
286
284
|
def make_delete_cookie_header(header, key, value)
|
287
285
|
case header
|
@@ -293,25 +291,30 @@ module Rack
|
|
293
291
|
cookies = header
|
294
292
|
end
|
295
293
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
294
|
+
key = escape(key)
|
295
|
+
domain = value[:domain]
|
296
|
+
path = value[:path]
|
297
|
+
regexp = if domain
|
298
|
+
if path
|
299
|
+
/\A#{key}=.*(?:domain=#{domain}(?:;|$).*path=#{path}(?:;|$)|path=#{path}(?:;|$).*domain=#{domain}(?:;|$))/
|
300
|
+
else
|
301
|
+
/\A#{key}=.*domain=#{domain}(?:;|$)/
|
302
|
+
end
|
303
|
+
elsif path
|
304
|
+
/\A#{key}=.*path=#{path}(?:;|$)/
|
300
305
|
else
|
301
|
-
/\A#{
|
306
|
+
/\A#{key}=/
|
302
307
|
end
|
303
308
|
|
304
309
|
cookies.reject! { |cookie| regexp.match? cookie }
|
305
310
|
|
306
311
|
cookies.join("\n")
|
307
312
|
end
|
308
|
-
module_function :make_delete_cookie_header
|
309
313
|
|
310
314
|
def delete_cookie_header!(header, key, value = {})
|
311
315
|
header[SET_COOKIE] = add_remove_cookie_to_header(header[SET_COOKIE], key, value)
|
312
316
|
nil
|
313
317
|
end
|
314
|
-
module_function :delete_cookie_header!
|
315
318
|
|
316
319
|
# Adds a cookie that will *remove* a cookie from the client. Hence the
|
317
320
|
# strange method name.
|
@@ -324,12 +327,10 @@ module Rack
|
|
324
327
|
expires: Time.at(0) }.merge(value))
|
325
328
|
|
326
329
|
end
|
327
|
-
module_function :add_remove_cookie_to_header
|
328
330
|
|
329
331
|
def rfc2822(time)
|
330
332
|
time.rfc2822
|
331
333
|
end
|
332
|
-
module_function :rfc2822
|
333
334
|
|
334
335
|
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
335
336
|
# of '% %b %Y'.
|
@@ -341,11 +342,10 @@ module Rack
|
|
341
342
|
# weekday and month.
|
342
343
|
#
|
343
344
|
def rfc2109(time)
|
344
|
-
wday =
|
345
|
-
mon =
|
345
|
+
wday = RFC2822_DAY_NAME[time.wday]
|
346
|
+
mon = RFC2822_MONTH_NAME[time.mon - 1]
|
346
347
|
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
347
348
|
end
|
348
|
-
module_function :rfc2109
|
349
349
|
|
350
350
|
# Parses the "Range:" header, if present, into an array of Range objects.
|
351
351
|
# Returns nil if the header is missing or syntactically invalid.
|
@@ -354,7 +354,6 @@ module Rack
|
|
354
354
|
warn "`byte_ranges` is deprecated, please use `get_byte_ranges`" if $VERBOSE
|
355
355
|
get_byte_ranges env['HTTP_RANGE'], size
|
356
356
|
end
|
357
|
-
module_function :byte_ranges
|
358
357
|
|
359
358
|
def get_byte_ranges(http_range, size)
|
360
359
|
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
@@ -382,9 +381,11 @@ module Rack
|
|
382
381
|
end
|
383
382
|
ranges << (r0..r1) if r0 <= r1
|
384
383
|
end
|
384
|
+
|
385
|
+
return [] if ranges.map(&:size).inject(0, :+) > size
|
386
|
+
|
385
387
|
ranges
|
386
388
|
end
|
387
|
-
module_function :get_byte_ranges
|
388
389
|
|
389
390
|
# Constant time string comparison.
|
390
391
|
#
|
@@ -401,7 +402,6 @@ module Rack
|
|
401
402
|
b.each_byte { |v| r |= v ^ l[i += 1] }
|
402
403
|
r == 0
|
403
404
|
end
|
404
|
-
module_function :secure_compare
|
405
405
|
|
406
406
|
# Context allows the use of a compatible middleware at different points
|
407
407
|
# in a request handling stack. A compatible middleware must define
|
@@ -434,6 +434,14 @@ module Rack
|
|
434
434
|
#
|
435
435
|
# @api private
|
436
436
|
class HeaderHash < Hash # :nodoc:
|
437
|
+
def self.[](headers)
|
438
|
+
if headers.is_a?(HeaderHash) && !headers.frozen?
|
439
|
+
return headers
|
440
|
+
else
|
441
|
+
return self.new(headers)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
437
445
|
def initialize(hash = {})
|
438
446
|
super()
|
439
447
|
@names = {}
|
@@ -446,6 +454,12 @@ module Rack
|
|
446
454
|
@names = other.names.dup
|
447
455
|
end
|
448
456
|
|
457
|
+
# on clear, we need to clear @names hash
|
458
|
+
def clear
|
459
|
+
super
|
460
|
+
@names.clear
|
461
|
+
end
|
462
|
+
|
449
463
|
def each
|
450
464
|
super do |k, v|
|
451
465
|
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
@@ -590,7 +604,6 @@ module Rack
|
|
590
604
|
status.to_i
|
591
605
|
end
|
592
606
|
end
|
593
|
-
module_function :status_code
|
594
607
|
|
595
608
|
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
596
609
|
|
@@ -604,18 +617,16 @@ module Rack
|
|
604
617
|
part == '..' ? clean.pop : clean << part
|
605
618
|
end
|
606
619
|
|
607
|
-
|
608
|
-
|
609
|
-
|
620
|
+
clean_path = clean.join(::File::SEPARATOR)
|
621
|
+
clean_path.prepend("/") if parts.empty? || parts.first.empty?
|
622
|
+
clean_path
|
610
623
|
end
|
611
|
-
module_function :clean_path_info
|
612
624
|
|
613
625
|
NULL_BYTE = "\0"
|
614
626
|
|
615
627
|
def valid_path?(path)
|
616
628
|
path.valid_encoding? && !path.include?(NULL_BYTE)
|
617
629
|
end
|
618
|
-
module_function :valid_path?
|
619
630
|
|
620
631
|
end
|
621
632
|
end
|