rack 1.1.6 → 1.6.9
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 +7 -0
- data/COPYING +1 -1
- data/HISTORY.md +375 -0
- data/KNOWN-ISSUES +23 -0
- data/README.rdoc +312 -0
- data/Rakefile +124 -0
- data/SPEC +125 -32
- data/contrib/rack.png +0 -0
- data/contrib/rack.svg +150 -0
- data/contrib/rack_logo.svg +1 -1
- data/contrib/rdoc.css +412 -0
- data/example/protectedlobster.rb +1 -1
- data/lib/rack/auth/abstract/handler.rb +4 -4
- data/lib/rack/auth/abstract/request.rb +7 -5
- data/lib/rack/auth/basic.rb +1 -1
- data/lib/rack/auth/digest/md5.rb +7 -3
- data/lib/rack/auth/digest/nonce.rb +1 -1
- data/lib/rack/auth/digest/params.rb +7 -9
- data/lib/rack/auth/digest/request.rb +10 -9
- data/lib/rack/backports/uri/common_18.rb +56 -0
- data/lib/rack/backports/uri/common_192.rb +52 -0
- data/lib/rack/backports/uri/common_193.rb +29 -0
- data/lib/rack/body_proxy.rb +39 -0
- data/lib/rack/builder.rb +106 -22
- data/lib/rack/cascade.rb +17 -6
- data/lib/rack/chunked.rb +44 -24
- data/lib/rack/commonlogger.rb +36 -13
- data/lib/rack/conditionalget.rb +49 -17
- data/lib/rack/config.rb +5 -0
- data/lib/rack/content_length.rb +14 -6
- data/lib/rack/content_type.rb +7 -1
- data/lib/rack/deflater.rb +73 -15
- data/lib/rack/directory.rb +18 -8
- data/lib/rack/etag.rb +59 -9
- data/lib/rack/file.rb +106 -44
- data/lib/rack/handler/cgi.rb +11 -11
- data/lib/rack/handler/fastcgi.rb +18 -6
- data/lib/rack/handler/lsws.rb +2 -4
- data/lib/rack/handler/mongrel.rb +22 -6
- data/lib/rack/handler/scgi.rb +16 -8
- data/lib/rack/handler/thin.rb +19 -4
- data/lib/rack/handler/webrick.rb +72 -19
- data/lib/rack/handler.rb +47 -14
- data/lib/rack/head.rb +10 -2
- data/lib/rack/lint.rb +260 -75
- data/lib/rack/lobster.rb +13 -8
- data/lib/rack/lock.rb +13 -3
- data/lib/rack/logger.rb +0 -2
- data/lib/rack/methodoverride.rb +27 -8
- data/lib/rack/mime.rb +625 -167
- data/lib/rack/mock.rb +78 -53
- data/lib/rack/multipart/generator.rb +93 -0
- data/lib/rack/multipart/parser.rb +253 -0
- data/lib/rack/multipart/uploaded_file.rb +34 -0
- data/lib/rack/multipart.rb +34 -0
- data/lib/rack/nulllogger.rb +21 -2
- data/lib/rack/recursive.rb +10 -5
- data/lib/rack/reloader.rb +3 -2
- data/lib/rack/request.rb +201 -74
- data/lib/rack/response.rb +41 -28
- data/lib/rack/rewindable_input.rb +15 -11
- data/lib/rack/runtime.rb +16 -3
- data/lib/rack/sendfile.rb +47 -29
- data/lib/rack/server.rb +223 -47
- data/lib/rack/session/abstract/id.rb +289 -30
- data/lib/rack/session/cookie.rb +133 -44
- data/lib/rack/session/memcache.rb +30 -56
- data/lib/rack/session/pool.rb +19 -43
- data/lib/rack/showexceptions.rb +53 -15
- data/lib/rack/showstatus.rb +14 -7
- data/lib/rack/static.rb +124 -12
- data/lib/rack/tempfile_reaper.rb +22 -0
- data/lib/rack/urlmap.rb +49 -15
- data/lib/rack/utils/okjson.rb +600 -0
- data/lib/rack/utils.rb +363 -361
- data/lib/rack.rb +17 -23
- data/rack.gemspec +11 -20
- data/test/builder/anything.rb +5 -0
- data/test/builder/comment.ru +4 -0
- data/test/builder/end.ru +5 -0
- data/test/builder/line.ru +1 -0
- data/test/builder/options.ru +2 -0
- data/test/cgi/assets/folder/test.js +1 -0
- data/test/cgi/assets/fonts/font.eot +1 -0
- data/test/cgi/assets/images/image.png +1 -0
- data/test/cgi/assets/index.html +1 -0
- data/test/cgi/assets/javascripts/app.js +1 -0
- data/test/cgi/assets/stylesheets/app.css +1 -0
- data/test/cgi/lighttpd.conf +26 -0
- data/test/cgi/rackup_stub.rb +6 -0
- data/test/cgi/sample_rackup.ru +5 -0
- data/test/cgi/test +9 -0
- data/test/cgi/test+directory/test+file +1 -0
- data/test/cgi/test.fcgi +8 -0
- data/test/cgi/test.ru +5 -0
- data/test/gemloader.rb +10 -0
- data/test/multipart/bad_robots +259 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/content_type_and_no_filename +6 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/fail_16384_nofile +814 -0
- data/test/multipart/file1.txt +1 -0
- data/test/multipart/filename_and_modification_param +7 -0
- data/test/multipart/filename_and_no_name +6 -0
- data/test/multipart/filename_with_escaped_quotes +6 -0
- data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
- data/test/multipart/filename_with_null_byte +7 -0
- data/test/multipart/filename_with_percent_escaped_quotes +6 -0
- data/test/multipart/filename_with_unescaped_percentages +6 -0
- data/test/multipart/filename_with_unescaped_percentages2 +6 -0
- data/test/multipart/filename_with_unescaped_percentages3 +6 -0
- data/test/multipart/filename_with_unescaped_quotes +6 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/invalid_character +6 -0
- data/test/multipart/mixed_files +21 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/semicolon +6 -0
- data/test/multipart/text +15 -0
- data/test/multipart/three_files_three_fields +31 -0
- data/test/multipart/webkit +32 -0
- data/test/rackup/config.ru +31 -0
- data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
- data/test/{spec_rack_auth_basic.rb → spec_auth_basic.rb} +23 -15
- data/test/{spec_rack_auth_digest.rb → spec_auth_digest.rb} +56 -29
- data/test/spec_body_proxy.rb +85 -0
- data/test/spec_builder.rb +223 -0
- data/test/{spec_rack_cascade.rb → spec_cascade.rb} +28 -15
- data/test/{spec_rack_cgi.rb → spec_cgi.rb} +44 -31
- data/test/spec_chunked.rb +101 -0
- data/test/spec_commonlogger.rb +93 -0
- data/test/spec_conditionalget.rb +102 -0
- data/test/{spec_rack_config.rb → spec_config.rb} +6 -8
- data/test/spec_content_length.rb +85 -0
- data/test/spec_content_type.rb +45 -0
- data/test/spec_deflater.rb +339 -0
- data/test/{spec_rack_directory.rb → spec_directory.rb} +37 -10
- data/test/spec_etag.rb +107 -0
- data/test/{spec_rack_fastcgi.rb → spec_fastcgi.rb} +47 -29
- data/test/spec_file.rb +221 -0
- data/test/spec_handler.rb +72 -0
- data/test/spec_head.rb +45 -0
- data/test/{spec_rack_lint.rb → spec_lint.rb} +82 -60
- data/test/spec_lobster.rb +58 -0
- data/test/spec_lock.rb +164 -0
- data/test/spec_logger.rb +23 -0
- data/test/spec_methodoverride.rb +95 -0
- data/test/spec_mime.rb +51 -0
- data/test/{spec_rack_mock.rb → spec_mock.rb} +92 -38
- data/test/{spec_rack_mongrel.rb → spec_mongrel.rb} +46 -53
- data/test/spec_multipart.rb +600 -0
- data/test/spec_nulllogger.rb +20 -0
- data/test/spec_recursive.rb +72 -0
- data/test/spec_request.rb +1227 -0
- data/test/spec_response.rb +407 -0
- data/test/spec_rewindable_input.rb +118 -0
- data/test/spec_runtime.rb +49 -0
- data/test/spec_sendfile.rb +130 -0
- data/test/spec_server.rb +167 -0
- data/test/spec_session_abstract_id.rb +53 -0
- data/test/spec_session_cookie.rb +410 -0
- data/test/{spec_rack_session_memcache.rb → spec_session_memcache.rb} +119 -71
- data/test/{spec_rack_session_pool.rb → spec_session_pool.rb} +106 -69
- data/test/spec_showexceptions.rb +85 -0
- data/test/spec_showstatus.rb +103 -0
- data/test/spec_static.rb +145 -0
- data/test/spec_tempfile_reaper.rb +63 -0
- data/test/{spec_rack_thin.rb → spec_thin.rb} +35 -35
- data/test/{spec_rack_urlmap.rb → spec_urlmap.rb} +40 -19
- data/test/spec_utils.rb +647 -0
- data/test/spec_version.rb +17 -0
- data/test/spec_webrick.rb +184 -0
- data/test/static/another/index.html +1 -0
- data/test/static/index.html +1 -0
- data/test/testrequest.rb +78 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +220 -239
- data/RDOX +0 -0
- data/README +0 -592
- data/lib/rack/adapter/camping.rb +0 -22
- data/test/spec_auth.rb +0 -57
- data/test/spec_rack_builder.rb +0 -84
- data/test/spec_rack_camping.rb +0 -55
- data/test/spec_rack_chunked.rb +0 -62
- data/test/spec_rack_commonlogger.rb +0 -61
- data/test/spec_rack_conditionalget.rb +0 -41
- data/test/spec_rack_content_length.rb +0 -43
- data/test/spec_rack_content_type.rb +0 -30
- data/test/spec_rack_deflater.rb +0 -127
- data/test/spec_rack_etag.rb +0 -17
- data/test/spec_rack_file.rb +0 -75
- data/test/spec_rack_handler.rb +0 -43
- data/test/spec_rack_head.rb +0 -30
- data/test/spec_rack_lobster.rb +0 -45
- data/test/spec_rack_lock.rb +0 -38
- data/test/spec_rack_logger.rb +0 -21
- data/test/spec_rack_methodoverride.rb +0 -60
- data/test/spec_rack_nulllogger.rb +0 -13
- data/test/spec_rack_recursive.rb +0 -77
- data/test/spec_rack_request.rb +0 -594
- data/test/spec_rack_response.rb +0 -221
- data/test/spec_rack_rewindable_input.rb +0 -118
- data/test/spec_rack_runtime.rb +0 -35
- data/test/spec_rack_sendfile.rb +0 -86
- data/test/spec_rack_session_cookie.rb +0 -92
- data/test/spec_rack_showexceptions.rb +0 -21
- data/test/spec_rack_showstatus.rb +0 -72
- data/test/spec_rack_static.rb +0 -37
- data/test/spec_rack_utils.rb +0 -557
- data/test/spec_rack_webrick.rb +0 -130
- data/test/spec_rackup.rb +0 -164
data/lib/rack/utils.rb
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
|
-
|
|
2
|
+
require 'fileutils'
|
|
3
3
|
require 'set'
|
|
4
4
|
require 'tempfile'
|
|
5
|
+
require 'rack/multipart'
|
|
6
|
+
require 'time'
|
|
7
|
+
|
|
8
|
+
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
|
|
9
|
+
|
|
10
|
+
if major == 1 && minor < 9
|
|
11
|
+
require 'rack/backports/uri/common_18'
|
|
12
|
+
elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
|
|
13
|
+
require 'rack/backports/uri/common_192'
|
|
14
|
+
elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
|
|
15
|
+
require 'rack/backports/uri/common_193'
|
|
16
|
+
else
|
|
17
|
+
require 'uri/common'
|
|
18
|
+
end
|
|
5
19
|
|
|
6
20
|
module Rack
|
|
7
21
|
# Rack::Utils contains a grab-bag of useful methods for writing web
|
|
8
22
|
# applications adopted from all kinds of Ruby libraries.
|
|
9
23
|
|
|
10
24
|
module Utils
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
25
|
+
# ParameterTypeError is the error that is raised when incoming structural
|
|
26
|
+
# parameters (parsed by parse_nested_query) contain conflicting types.
|
|
27
|
+
class ParameterTypeError < TypeError; end
|
|
28
|
+
|
|
29
|
+
# InvalidParameterError is the error that is raised when incoming structural
|
|
30
|
+
# parameters (parsed by parse_nested_query) contain invalid format or byte
|
|
31
|
+
# sequence.
|
|
32
|
+
class InvalidParameterError < ArgumentError; end
|
|
33
|
+
|
|
34
|
+
# URI escapes. (CGI style space to +)
|
|
14
35
|
def escape(s)
|
|
15
|
-
|
|
16
|
-
'%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
|
|
17
|
-
}.tr(' ', '+')
|
|
36
|
+
URI.encode_www_form_component(s)
|
|
18
37
|
end
|
|
19
38
|
module_function :escape
|
|
20
39
|
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
# Like URI escaping, but with %20 instead of +. Strictly speaking this is
|
|
41
|
+
# true URI escaping.
|
|
42
|
+
def escape_path(s)
|
|
43
|
+
escape(s).gsub('+', '%20')
|
|
44
|
+
end
|
|
45
|
+
module_function :escape_path
|
|
46
|
+
|
|
47
|
+
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
|
48
|
+
# target encoding of the string returned, and it defaults to UTF-8
|
|
49
|
+
if defined?(::Encoding)
|
|
50
|
+
def unescape(s, encoding = Encoding::UTF_8)
|
|
51
|
+
URI.decode_www_form_component(s, encoding)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
def unescape(s, encoding = nil)
|
|
55
|
+
URI.decode_www_form_component(s, encoding)
|
|
56
|
+
end
|
|
26
57
|
end
|
|
27
58
|
module_function :unescape
|
|
28
59
|
|
|
@@ -30,32 +61,37 @@ module Rack
|
|
|
30
61
|
|
|
31
62
|
class << self
|
|
32
63
|
attr_accessor :key_space_limit
|
|
64
|
+
attr_accessor :param_depth_limit
|
|
65
|
+
attr_accessor :multipart_part_limit
|
|
33
66
|
end
|
|
34
67
|
|
|
35
68
|
# The default number of bytes to allow parameter keys to take up.
|
|
36
69
|
# This helps prevent a rogue client from flooding a Request.
|
|
37
70
|
self.key_space_limit = 65536
|
|
38
71
|
|
|
72
|
+
# Default depth at which the parameter parser will raise an exception for
|
|
73
|
+
# being too deep. This helps prevent SystemStackErrors
|
|
74
|
+
self.param_depth_limit = 100
|
|
75
|
+
|
|
76
|
+
# The maximum number of parts a request can contain. Accepting too many part
|
|
77
|
+
# can lead to the server running out of file handles.
|
|
78
|
+
# Set to `0` for no limit.
|
|
79
|
+
# FIXME: RACK_MULTIPART_LIMIT was introduced by mistake and it will be removed in 1.7.0
|
|
80
|
+
self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_LIMIT'] || 128).to_i
|
|
81
|
+
|
|
39
82
|
# Stolen from Mongrel, with some small modifications:
|
|
40
83
|
# Parses a query string by breaking it up at the '&'
|
|
41
84
|
# and ';' characters. You can also use this to parse
|
|
42
85
|
# cookies by changing the characters used in the second
|
|
43
86
|
# parameter (which defaults to '&;').
|
|
44
|
-
def parse_query(qs, d = nil)
|
|
45
|
-
|
|
87
|
+
def parse_query(qs, d = nil, &unescaper)
|
|
88
|
+
unescaper ||= method(:unescape)
|
|
46
89
|
|
|
47
|
-
|
|
48
|
-
bytes = 0
|
|
90
|
+
params = KeySpaceConstrainedParams.new
|
|
49
91
|
|
|
50
92
|
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if k
|
|
54
|
-
bytes += k.size
|
|
55
|
-
if bytes > max_key_space
|
|
56
|
-
raise RangeError, "exceeded available parameter key space"
|
|
57
|
-
end
|
|
58
|
-
end
|
|
93
|
+
next if p.empty?
|
|
94
|
+
k, v = p.split('=', 2).map(&unescaper)
|
|
59
95
|
|
|
60
96
|
if cur = params[k]
|
|
61
97
|
if cur.class == Array
|
|
@@ -68,34 +104,36 @@ module Rack
|
|
|
68
104
|
end
|
|
69
105
|
end
|
|
70
106
|
|
|
71
|
-
return params
|
|
107
|
+
return params.to_params_hash
|
|
72
108
|
end
|
|
73
109
|
module_function :parse_query
|
|
74
110
|
|
|
111
|
+
# parse_nested_query expands a query string into structural types. Supported
|
|
112
|
+
# types are Arrays, Hashes and basic value types. It is possible to supply
|
|
113
|
+
# query strings with parameters of conflicting types, in this case a
|
|
114
|
+
# ParameterTypeError is raised. Users are encouraged to return a 400 in this
|
|
115
|
+
# case.
|
|
75
116
|
def parse_nested_query(qs, d = nil)
|
|
76
|
-
params =
|
|
77
|
-
|
|
78
|
-
max_key_space = Utils.key_space_limit
|
|
79
|
-
bytes = 0
|
|
117
|
+
params = KeySpaceConstrainedParams.new
|
|
80
118
|
|
|
81
119
|
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
|
82
|
-
k, v =
|
|
83
|
-
|
|
84
|
-
if k
|
|
85
|
-
bytes += k.size
|
|
86
|
-
if bytes > max_key_space
|
|
87
|
-
raise RangeError, "exceeded available parameter key space"
|
|
88
|
-
end
|
|
89
|
-
end
|
|
120
|
+
k, v = p.split('=', 2).map { |s| unescape(s) }
|
|
90
121
|
|
|
91
122
|
normalize_params(params, k, v)
|
|
92
123
|
end
|
|
93
124
|
|
|
94
|
-
return params
|
|
125
|
+
return params.to_params_hash
|
|
126
|
+
rescue ArgumentError => e
|
|
127
|
+
raise InvalidParameterError, e.message
|
|
95
128
|
end
|
|
96
129
|
module_function :parse_nested_query
|
|
97
130
|
|
|
98
|
-
|
|
131
|
+
# normalize_params recursively expands parameters into structural types. If
|
|
132
|
+
# the structural types represented by two different parameter names are in
|
|
133
|
+
# conflict, a ParameterTypeError is raised.
|
|
134
|
+
def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit)
|
|
135
|
+
raise RangeError if depth <= 0
|
|
136
|
+
|
|
99
137
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
|
100
138
|
k = $1 || ''
|
|
101
139
|
after = $' || ''
|
|
@@ -104,35 +142,42 @@ module Rack
|
|
|
104
142
|
|
|
105
143
|
if after == ""
|
|
106
144
|
params[k] = v
|
|
145
|
+
elsif after == "["
|
|
146
|
+
params[name] = v
|
|
107
147
|
elsif after == "[]"
|
|
108
148
|
params[k] ||= []
|
|
109
|
-
raise
|
|
149
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
110
150
|
params[k] << v
|
|
111
151
|
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
|
112
152
|
child_key = $1
|
|
113
153
|
params[k] ||= []
|
|
114
|
-
raise
|
|
115
|
-
if params[k].last
|
|
116
|
-
normalize_params(params[k].last, child_key, v)
|
|
154
|
+
raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
|
155
|
+
if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
|
|
156
|
+
normalize_params(params[k].last, child_key, v, depth - 1)
|
|
117
157
|
else
|
|
118
|
-
params[k] << normalize_params(
|
|
158
|
+
params[k] << normalize_params(params.class.new, child_key, v, depth - 1)
|
|
119
159
|
end
|
|
120
160
|
else
|
|
121
|
-
params[k] ||=
|
|
122
|
-
raise
|
|
123
|
-
params[k] = normalize_params(params[k], after, v)
|
|
161
|
+
params[k] ||= params.class.new
|
|
162
|
+
raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
|
|
163
|
+
params[k] = normalize_params(params[k], after, v, depth - 1)
|
|
124
164
|
end
|
|
125
165
|
|
|
126
166
|
return params
|
|
127
167
|
end
|
|
128
168
|
module_function :normalize_params
|
|
129
169
|
|
|
170
|
+
def params_hash_type?(obj)
|
|
171
|
+
obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash)
|
|
172
|
+
end
|
|
173
|
+
module_function :params_hash_type?
|
|
174
|
+
|
|
130
175
|
def build_query(params)
|
|
131
176
|
params.map { |k, v|
|
|
132
177
|
if v.class == Array
|
|
133
178
|
build_query(v.map { |x| [k, x] })
|
|
134
179
|
else
|
|
135
|
-
"#{escape(k)}=#{escape(v)}"
|
|
180
|
+
v.nil? ? escape(k) : "#{escape(k)}=#{escape(v)}"
|
|
136
181
|
end
|
|
137
182
|
}.join("&")
|
|
138
183
|
end
|
|
@@ -147,23 +192,61 @@ module Rack
|
|
|
147
192
|
when Hash
|
|
148
193
|
value.map { |k, v|
|
|
149
194
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
|
150
|
-
}.join(
|
|
151
|
-
when
|
|
195
|
+
}.reject(&:empty?).join('&')
|
|
196
|
+
when nil
|
|
197
|
+
prefix
|
|
198
|
+
else
|
|
152
199
|
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
|
153
200
|
"#{prefix}=#{escape(value)}"
|
|
154
|
-
else
|
|
155
|
-
prefix
|
|
156
201
|
end
|
|
157
202
|
end
|
|
158
203
|
module_function :build_nested_query
|
|
159
204
|
|
|
205
|
+
def q_values(q_value_header)
|
|
206
|
+
q_value_header.to_s.split(/\s*,\s*/).map do |part|
|
|
207
|
+
value, parameters = part.split(/\s*;\s*/, 2)
|
|
208
|
+
quality = 1.0
|
|
209
|
+
if md = /\Aq=([\d.]+)/.match(parameters)
|
|
210
|
+
quality = md[1].to_f
|
|
211
|
+
end
|
|
212
|
+
[value, quality]
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
module_function :q_values
|
|
216
|
+
|
|
217
|
+
def best_q_match(q_value_header, available_mimes)
|
|
218
|
+
values = q_values(q_value_header)
|
|
219
|
+
|
|
220
|
+
matches = values.map do |req_mime, quality|
|
|
221
|
+
match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
|
|
222
|
+
next unless match
|
|
223
|
+
[match, quality]
|
|
224
|
+
end.compact.sort_by do |match, quality|
|
|
225
|
+
(match.split('/', 2).count('*') * -10) + quality
|
|
226
|
+
end.last
|
|
227
|
+
matches && matches.first
|
|
228
|
+
end
|
|
229
|
+
module_function :best_q_match
|
|
230
|
+
|
|
231
|
+
ESCAPE_HTML = {
|
|
232
|
+
"&" => "&",
|
|
233
|
+
"<" => "<",
|
|
234
|
+
">" => ">",
|
|
235
|
+
"'" => "'",
|
|
236
|
+
'"' => """,
|
|
237
|
+
"/" => "/"
|
|
238
|
+
}
|
|
239
|
+
if //.respond_to?(:encoding)
|
|
240
|
+
ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
|
|
241
|
+
else
|
|
242
|
+
# On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise
|
|
243
|
+
# TODO doesn't apply to jruby, so a better condition above might be preferable?
|
|
244
|
+
ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
|
|
245
|
+
end
|
|
246
|
+
|
|
160
247
|
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
|
161
248
|
def escape_html(string)
|
|
162
|
-
string.to_s.gsub(
|
|
163
|
-
gsub("<", "<").
|
|
164
|
-
gsub(">", ">").
|
|
165
|
-
gsub("'", "'").
|
|
166
|
-
gsub('"', """)
|
|
249
|
+
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
|
|
167
250
|
end
|
|
168
251
|
module_function :escape_html
|
|
169
252
|
|
|
@@ -187,10 +270,8 @@ module Rack
|
|
|
187
270
|
encoding_candidates.push("identity")
|
|
188
271
|
end
|
|
189
272
|
|
|
190
|
-
expanded_accept_encoding.
|
|
191
|
-
q == 0.0
|
|
192
|
-
}.each { |m, _|
|
|
193
|
-
encoding_candidates.delete(m)
|
|
273
|
+
expanded_accept_encoding.each { |m, q|
|
|
274
|
+
encoding_candidates.delete(m) if q == 0.0
|
|
194
275
|
}
|
|
195
276
|
|
|
196
277
|
return (encoding_candidates & available_encodings)[0]
|
|
@@ -200,20 +281,53 @@ module Rack
|
|
|
200
281
|
def set_cookie_header!(header, key, value)
|
|
201
282
|
case value
|
|
202
283
|
when Hash
|
|
203
|
-
domain = "; domain=" + value[:domain]
|
|
204
|
-
path = "; path=" + value[:path]
|
|
205
|
-
|
|
206
|
-
#
|
|
284
|
+
domain = "; domain=" + value[:domain] if value[:domain]
|
|
285
|
+
path = "; path=" + value[:path] if value[:path]
|
|
286
|
+
max_age = "; max-age=" + value[:max_age].to_s if value[:max_age]
|
|
287
|
+
# There is an RFC mess in the area of date formatting for Cookies. Not
|
|
288
|
+
# only are there contradicting RFCs and examples within RFC text, but
|
|
289
|
+
# there are also numerous conflicting names of fields and partially
|
|
290
|
+
# cross-applicable specifications.
|
|
291
|
+
#
|
|
292
|
+
# These are best described in RFC 2616 3.3.1. This RFC text also
|
|
293
|
+
# specifies that RFC 822 as updated by RFC 1123 is preferred. That is a
|
|
294
|
+
# fixed length format with space-date delimeted fields.
|
|
295
|
+
#
|
|
296
|
+
# See also RFC 1123 section 5.2.14.
|
|
297
|
+
#
|
|
298
|
+
# RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined
|
|
299
|
+
# in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote
|
|
300
|
+
# the space delimited format. These formats are compliant with RFC 2822.
|
|
301
|
+
#
|
|
302
|
+
# For reference, all involved RFCs are:
|
|
303
|
+
# RFC 822
|
|
304
|
+
# RFC 1123
|
|
305
|
+
# RFC 2109
|
|
306
|
+
# RFC 2616
|
|
307
|
+
# RFC 2822
|
|
308
|
+
# RFC 2965
|
|
309
|
+
# RFC 6265
|
|
207
310
|
expires = "; expires=" +
|
|
208
311
|
rfc2822(value[:expires].clone.gmtime) if value[:expires]
|
|
209
312
|
secure = "; secure" if value[:secure]
|
|
210
|
-
httponly = "; HttpOnly" if value[:httponly]
|
|
313
|
+
httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
|
|
314
|
+
same_site =
|
|
315
|
+
case value[:same_site]
|
|
316
|
+
when false, nil
|
|
317
|
+
nil
|
|
318
|
+
when :lax, 'Lax', :Lax
|
|
319
|
+
'; SameSite=Lax'.freeze
|
|
320
|
+
when true, :strict, 'Strict', :Strict
|
|
321
|
+
'; SameSite=Strict'.freeze
|
|
322
|
+
else
|
|
323
|
+
raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
|
|
324
|
+
end
|
|
211
325
|
value = value[:value]
|
|
212
326
|
end
|
|
213
327
|
value = [value] unless Array === value
|
|
214
328
|
cookie = escape(key) + "=" +
|
|
215
329
|
value.map { |v| escape v }.join("&") +
|
|
216
|
-
"#{domain}#{path}#{expires}#{secure}#{httponly}"
|
|
330
|
+
"#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
|
|
217
331
|
|
|
218
332
|
case header["Set-Cookie"]
|
|
219
333
|
when nil, ''
|
|
@@ -241,6 +355,8 @@ module Rack
|
|
|
241
355
|
cookies.reject! { |cookie|
|
|
242
356
|
if value[:domain]
|
|
243
357
|
cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
|
|
358
|
+
elsif value[:path]
|
|
359
|
+
cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/
|
|
244
360
|
else
|
|
245
361
|
cookie =~ /\A#{escape(key)}=/
|
|
246
362
|
end
|
|
@@ -250,42 +366,86 @@ module Rack
|
|
|
250
366
|
|
|
251
367
|
set_cookie_header!(header, key,
|
|
252
368
|
{:value => '', :path => nil, :domain => nil,
|
|
369
|
+
:max_age => '0',
|
|
253
370
|
:expires => Time.at(0) }.merge(value))
|
|
254
371
|
|
|
255
372
|
nil
|
|
256
373
|
end
|
|
257
374
|
module_function :delete_cookie_header!
|
|
258
375
|
|
|
376
|
+
# Return the bytesize of String; uses String#size under Ruby 1.8 and
|
|
377
|
+
# String#bytesize under 1.9.
|
|
378
|
+
if ''.respond_to?(:bytesize)
|
|
379
|
+
def bytesize(string)
|
|
380
|
+
string.bytesize
|
|
381
|
+
end
|
|
382
|
+
else
|
|
383
|
+
def bytesize(string)
|
|
384
|
+
string.size
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
module_function :bytesize
|
|
388
|
+
|
|
389
|
+
def rfc2822(time)
|
|
390
|
+
time.rfc2822
|
|
391
|
+
end
|
|
392
|
+
module_function :rfc2822
|
|
393
|
+
|
|
259
394
|
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
|
|
260
395
|
# of '% %b %Y'.
|
|
261
396
|
# It assumes that the time is in GMT to comply to the RFC 2109.
|
|
262
397
|
#
|
|
263
|
-
# NOTE: I'm not sure the RFC says it requires GMT, but is
|
|
398
|
+
# NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough
|
|
264
399
|
# that I'm certain someone implemented only that option.
|
|
265
400
|
# Do not use %a and %b from Time.strptime, it would use localized names for
|
|
266
401
|
# weekday and month.
|
|
267
402
|
#
|
|
268
|
-
def
|
|
403
|
+
def rfc2109(time)
|
|
269
404
|
wday = Time::RFC2822_DAY_NAME[time.wday]
|
|
270
405
|
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
|
|
271
406
|
time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
|
|
272
407
|
end
|
|
273
|
-
module_function :
|
|
274
|
-
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
if
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
408
|
+
module_function :rfc2109
|
|
409
|
+
|
|
410
|
+
# Parses the "Range:" header, if present, into an array of Range objects.
|
|
411
|
+
# Returns nil if the header is missing or syntactically invalid.
|
|
412
|
+
# Returns an empty array if none of the ranges are satisfiable.
|
|
413
|
+
def byte_ranges(env, size)
|
|
414
|
+
# See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
|
|
415
|
+
http_range = env['HTTP_RANGE']
|
|
416
|
+
return nil unless http_range && http_range =~ /bytes=([^;]+)/
|
|
417
|
+
ranges = []
|
|
418
|
+
$1.split(/,\s*/).each do |range_spec|
|
|
419
|
+
return nil unless range_spec =~ /(\d*)-(\d*)/
|
|
420
|
+
r0,r1 = $1, $2
|
|
421
|
+
if r0.empty?
|
|
422
|
+
return nil if r1.empty?
|
|
423
|
+
# suffix-byte-range-spec, represents trailing suffix of file
|
|
424
|
+
r0 = size - r1.to_i
|
|
425
|
+
r0 = 0 if r0 < 0
|
|
426
|
+
r1 = size - 1
|
|
427
|
+
else
|
|
428
|
+
r0 = r0.to_i
|
|
429
|
+
if r1.empty?
|
|
430
|
+
r1 = size - 1
|
|
431
|
+
else
|
|
432
|
+
r1 = r1.to_i
|
|
433
|
+
return nil if r1 < r0 # backwards range is syntactically invalid
|
|
434
|
+
r1 = size-1 if r1 >= size
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
ranges << (r0..r1) if r0 <= r1
|
|
284
438
|
end
|
|
439
|
+
ranges
|
|
285
440
|
end
|
|
286
|
-
module_function :
|
|
441
|
+
module_function :byte_ranges
|
|
287
442
|
|
|
288
443
|
# Constant time string comparison.
|
|
444
|
+
#
|
|
445
|
+
# NOTE: the values compared should be of fixed length, such as strings
|
|
446
|
+
# that have already been processed by HMAC. This should not be used
|
|
447
|
+
# on variable length plaintext strings because it could leak length info
|
|
448
|
+
# via timing attacks.
|
|
289
449
|
def secure_compare(a, b)
|
|
290
450
|
return false unless bytesize(a) == bytesize(b)
|
|
291
451
|
|
|
@@ -343,23 +503,19 @@ module Rack
|
|
|
343
503
|
end
|
|
344
504
|
|
|
345
505
|
def to_hash
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
else
|
|
350
|
-
hash[k] = v
|
|
351
|
-
end
|
|
352
|
-
hash
|
|
353
|
-
end
|
|
506
|
+
hash = {}
|
|
507
|
+
each { |k,v| hash[k] = v }
|
|
508
|
+
hash
|
|
354
509
|
end
|
|
355
510
|
|
|
356
511
|
def [](k)
|
|
357
|
-
super(
|
|
512
|
+
super(k) || super(@names[k.downcase])
|
|
358
513
|
end
|
|
359
514
|
|
|
360
515
|
def []=(k, v)
|
|
361
|
-
|
|
362
|
-
@names[
|
|
516
|
+
canonical = k.downcase
|
|
517
|
+
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
|
518
|
+
@names[k] = @names[canonical] = k
|
|
363
519
|
super k, v
|
|
364
520
|
end
|
|
365
521
|
|
|
@@ -395,72 +551,116 @@ module Rack
|
|
|
395
551
|
end
|
|
396
552
|
end
|
|
397
553
|
|
|
554
|
+
class KeySpaceConstrainedParams
|
|
555
|
+
def initialize(limit = Utils.key_space_limit)
|
|
556
|
+
@limit = limit
|
|
557
|
+
@size = 0
|
|
558
|
+
@params = {}
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def [](key)
|
|
562
|
+
@params[key]
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def []=(key, value)
|
|
566
|
+
@size += key.size if key && !@params.key?(key)
|
|
567
|
+
raise RangeError, 'exceeded available parameter key space' if @size > @limit
|
|
568
|
+
@params[key] = value
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def key?(key)
|
|
572
|
+
@params.key?(key)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def to_params_hash
|
|
576
|
+
hash = @params
|
|
577
|
+
hash.keys.each do |key|
|
|
578
|
+
value = hash[key]
|
|
579
|
+
if value.kind_of?(self.class)
|
|
580
|
+
if value.object_id == self.object_id
|
|
581
|
+
hash[key] = hash
|
|
582
|
+
else
|
|
583
|
+
hash[key] = value.to_params_hash
|
|
584
|
+
end
|
|
585
|
+
elsif value.kind_of?(Array)
|
|
586
|
+
value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x}
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
hash
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
398
593
|
# Every standard HTTP code mapped to the appropriate message.
|
|
399
594
|
# Generated with:
|
|
400
|
-
#
|
|
401
|
-
#
|
|
402
|
-
#
|
|
595
|
+
# curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \
|
|
596
|
+
# ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.match($_) and \
|
|
597
|
+
# puts "#{m[1]} => \x27#{m[2].strip}\x27,"'
|
|
403
598
|
HTTP_STATUS_CODES = {
|
|
404
|
-
100
|
|
405
|
-
101
|
|
406
|
-
102
|
|
407
|
-
200
|
|
408
|
-
201
|
|
409
|
-
202
|
|
410
|
-
203
|
|
411
|
-
204
|
|
412
|
-
205
|
|
413
|
-
206
|
|
414
|
-
207
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
307
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
599
|
+
100 => 'Continue',
|
|
600
|
+
101 => 'Switching Protocols',
|
|
601
|
+
102 => 'Processing',
|
|
602
|
+
200 => 'OK',
|
|
603
|
+
201 => 'Created',
|
|
604
|
+
202 => 'Accepted',
|
|
605
|
+
203 => 'Non-Authoritative Information',
|
|
606
|
+
204 => 'No Content',
|
|
607
|
+
205 => 'Reset Content',
|
|
608
|
+
206 => 'Partial Content',
|
|
609
|
+
207 => 'Multi-Status',
|
|
610
|
+
208 => 'Already Reported',
|
|
611
|
+
226 => 'IM Used',
|
|
612
|
+
300 => 'Multiple Choices',
|
|
613
|
+
301 => 'Moved Permanently',
|
|
614
|
+
302 => 'Found',
|
|
615
|
+
303 => 'See Other',
|
|
616
|
+
304 => 'Not Modified',
|
|
617
|
+
305 => 'Use Proxy',
|
|
618
|
+
307 => 'Temporary Redirect',
|
|
619
|
+
308 => 'Permanent Redirect',
|
|
620
|
+
400 => 'Bad Request',
|
|
621
|
+
401 => 'Unauthorized',
|
|
622
|
+
402 => 'Payment Required',
|
|
623
|
+
403 => 'Forbidden',
|
|
624
|
+
404 => 'Not Found',
|
|
625
|
+
405 => 'Method Not Allowed',
|
|
626
|
+
406 => 'Not Acceptable',
|
|
627
|
+
407 => 'Proxy Authentication Required',
|
|
628
|
+
408 => 'Request Timeout',
|
|
629
|
+
409 => 'Conflict',
|
|
630
|
+
410 => 'Gone',
|
|
631
|
+
411 => 'Length Required',
|
|
632
|
+
412 => 'Precondition Failed',
|
|
633
|
+
413 => 'Payload Too Large',
|
|
634
|
+
414 => 'URI Too Long',
|
|
635
|
+
415 => 'Unsupported Media Type',
|
|
636
|
+
416 => 'Range Not Satisfiable',
|
|
637
|
+
417 => 'Expectation Failed',
|
|
638
|
+
422 => 'Unprocessable Entity',
|
|
639
|
+
423 => 'Locked',
|
|
640
|
+
424 => 'Failed Dependency',
|
|
641
|
+
426 => 'Upgrade Required',
|
|
642
|
+
428 => 'Precondition Required',
|
|
643
|
+
429 => 'Too Many Requests',
|
|
644
|
+
431 => 'Request Header Fields Too Large',
|
|
645
|
+
500 => 'Internal Server Error',
|
|
646
|
+
501 => 'Not Implemented',
|
|
647
|
+
502 => 'Bad Gateway',
|
|
648
|
+
503 => 'Service Unavailable',
|
|
649
|
+
504 => 'Gateway Timeout',
|
|
650
|
+
505 => 'HTTP Version Not Supported',
|
|
651
|
+
506 => 'Variant Also Negotiates',
|
|
652
|
+
507 => 'Insufficient Storage',
|
|
653
|
+
508 => 'Loop Detected',
|
|
654
|
+
510 => 'Not Extended',
|
|
655
|
+
511 => 'Network Authentication Required'
|
|
455
656
|
}
|
|
456
657
|
|
|
457
658
|
# Responses with HTTP status codes that should not have an entity body
|
|
458
|
-
STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
|
|
659
|
+
STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304)
|
|
459
660
|
|
|
460
|
-
SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
661
|
+
SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
|
|
662
|
+
[message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
|
|
663
|
+
}.flatten]
|
|
464
664
|
|
|
465
665
|
def status_code(status)
|
|
466
666
|
if status.is_a?(Symbol)
|
|
@@ -471,223 +671,25 @@ module Rack
|
|
|
471
671
|
end
|
|
472
672
|
module_function :status_code
|
|
473
673
|
|
|
474
|
-
|
|
475
|
-
#
|
|
476
|
-
# Usually, Rack::Request#POST takes care of calling this.
|
|
477
|
-
|
|
478
|
-
module Multipart
|
|
479
|
-
class UploadedFile
|
|
480
|
-
# The filename, *not* including the path, of the "uploaded" file
|
|
481
|
-
attr_reader :original_filename
|
|
482
|
-
|
|
483
|
-
# The content type of the "uploaded" file
|
|
484
|
-
attr_accessor :content_type
|
|
485
|
-
|
|
486
|
-
def initialize(path, content_type = "text/plain", binary = false)
|
|
487
|
-
raise "#{path} file does not exist" unless ::File.exist?(path)
|
|
488
|
-
@content_type = content_type
|
|
489
|
-
@original_filename = ::File.basename(path)
|
|
490
|
-
@tempfile = Tempfile.new(@original_filename)
|
|
491
|
-
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
|
492
|
-
@tempfile.binmode if binary
|
|
493
|
-
FileUtils.copy_file(path, @tempfile.path)
|
|
494
|
-
end
|
|
495
|
-
|
|
496
|
-
def path
|
|
497
|
-
@tempfile.path
|
|
498
|
-
end
|
|
499
|
-
alias_method :local_path, :path
|
|
500
|
-
|
|
501
|
-
def method_missing(method_name, *args, &block) #:nodoc:
|
|
502
|
-
@tempfile.__send__(method_name, *args, &block)
|
|
503
|
-
end
|
|
504
|
-
end
|
|
505
|
-
|
|
506
|
-
EOL = "\r\n"
|
|
507
|
-
MULTIPART_BOUNDARY = "AaB03x"
|
|
508
|
-
|
|
509
|
-
def self.parse_multipart(env)
|
|
510
|
-
unless env['CONTENT_TYPE'] =~
|
|
511
|
-
%r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
|
|
512
|
-
nil
|
|
513
|
-
else
|
|
514
|
-
boundary = "--#{$1}"
|
|
515
|
-
|
|
516
|
-
params = {}
|
|
517
|
-
buf = ""
|
|
518
|
-
content_length = env['CONTENT_LENGTH'].to_i
|
|
519
|
-
input = env['rack.input']
|
|
520
|
-
input.rewind
|
|
521
|
-
|
|
522
|
-
boundary_size = Utils.bytesize(boundary) + EOL.size
|
|
523
|
-
bufsize = 16384
|
|
674
|
+
Multipart = Rack::Multipart
|
|
524
675
|
|
|
525
|
-
|
|
676
|
+
PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
|
|
526
677
|
|
|
527
|
-
|
|
678
|
+
def clean_path_info(path_info)
|
|
679
|
+
parts = path_info.split PATH_SEPS
|
|
528
680
|
|
|
529
|
-
|
|
530
|
-
raise EOFError, "bad content body" unless status == boundary + EOL
|
|
681
|
+
clean = []
|
|
531
682
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
bytes = 0
|
|
536
|
-
|
|
537
|
-
loop {
|
|
538
|
-
head = nil
|
|
539
|
-
body = ''
|
|
540
|
-
filename = content_type = name = nil
|
|
541
|
-
|
|
542
|
-
until head && buf =~ rx
|
|
543
|
-
if !head && i = buf.index(EOL+EOL)
|
|
544
|
-
head = buf.slice!(0, i+2) # First \r\n
|
|
545
|
-
buf.slice!(0, 2) # Second \r\n
|
|
546
|
-
|
|
547
|
-
filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1]
|
|
548
|
-
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
|
|
549
|
-
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
|
|
550
|
-
|
|
551
|
-
if name
|
|
552
|
-
bytes += name.size
|
|
553
|
-
if bytes > max_key_space
|
|
554
|
-
raise RangeError, "exceeded available parameter key space"
|
|
555
|
-
end
|
|
556
|
-
end
|
|
557
|
-
|
|
558
|
-
if content_type || filename
|
|
559
|
-
body = Tempfile.new("RackMultipart")
|
|
560
|
-
body.binmode if body.respond_to?(:binmode)
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
next
|
|
564
|
-
end
|
|
565
|
-
|
|
566
|
-
# Save the read body part.
|
|
567
|
-
if head && (boundary_size+4 < buf.size)
|
|
568
|
-
body << buf.slice!(0, buf.size - (boundary_size+4))
|
|
569
|
-
end
|
|
570
|
-
|
|
571
|
-
c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
|
|
572
|
-
raise EOFError, "bad content body" if c.nil? || c.empty?
|
|
573
|
-
buf << c
|
|
574
|
-
content_length -= c.size
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
# Save the rest.
|
|
578
|
-
if i = buf.index(rx)
|
|
579
|
-
body << buf.slice!(0, i)
|
|
580
|
-
buf.slice!(0, boundary_size+2)
|
|
581
|
-
|
|
582
|
-
content_length = -1 if $1 == "--"
|
|
583
|
-
end
|
|
584
|
-
|
|
585
|
-
if filename == ""
|
|
586
|
-
# filename is blank which means no file has been selected
|
|
587
|
-
data = nil
|
|
588
|
-
elsif filename
|
|
589
|
-
body.rewind
|
|
590
|
-
|
|
591
|
-
# Take the basename of the upload's original filename.
|
|
592
|
-
# This handles the full Windows paths given by Internet Explorer
|
|
593
|
-
# (and perhaps other broken user agents) without affecting
|
|
594
|
-
# those which give the lone filename.
|
|
595
|
-
filename =~ /^(?:.*[:\\\/])?(.*)/m
|
|
596
|
-
filename = $1
|
|
597
|
-
|
|
598
|
-
data = {:filename => filename, :type => content_type,
|
|
599
|
-
:name => name, :tempfile => body, :head => head}
|
|
600
|
-
elsif !filename && content_type
|
|
601
|
-
body.rewind
|
|
602
|
-
|
|
603
|
-
# Generic multipart cases, not coming from a form
|
|
604
|
-
data = {:type => content_type,
|
|
605
|
-
:name => name, :tempfile => body, :head => head}
|
|
606
|
-
else
|
|
607
|
-
data = body
|
|
608
|
-
end
|
|
609
|
-
|
|
610
|
-
Utils.normalize_params(params, name, data) unless data.nil?
|
|
611
|
-
|
|
612
|
-
# break if we're at the end of a buffer, but not if it is the end of a field
|
|
613
|
-
break if (buf.empty? && $1 != EOL) || content_length == -1
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
input.rewind
|
|
617
|
-
|
|
618
|
-
params
|
|
619
|
-
end
|
|
683
|
+
parts.each do |part|
|
|
684
|
+
next if part.empty? || part == '.'
|
|
685
|
+
part == '..' ? clean.pop : clean << part
|
|
620
686
|
end
|
|
621
687
|
|
|
622
|
-
|
|
623
|
-
if first
|
|
624
|
-
unless params.is_a?(Hash)
|
|
625
|
-
raise ArgumentError, "value must be a Hash"
|
|
626
|
-
end
|
|
688
|
+
clean.unshift '/' if parts.empty? || parts.first.empty?
|
|
627
689
|
|
|
628
|
-
|
|
629
|
-
query = lambda { |value|
|
|
630
|
-
case value
|
|
631
|
-
when Array
|
|
632
|
-
value.each(&query)
|
|
633
|
-
when Hash
|
|
634
|
-
value.values.each(&query)
|
|
635
|
-
when UploadedFile
|
|
636
|
-
multipart = true
|
|
637
|
-
end
|
|
638
|
-
}
|
|
639
|
-
params.values.each(&query)
|
|
640
|
-
return nil unless multipart
|
|
641
|
-
end
|
|
642
|
-
|
|
643
|
-
flattened_params = Hash.new
|
|
644
|
-
|
|
645
|
-
params.each do |key, value|
|
|
646
|
-
k = first ? key.to_s : "[#{key}]"
|
|
647
|
-
|
|
648
|
-
case value
|
|
649
|
-
when Array
|
|
650
|
-
value.map { |v|
|
|
651
|
-
build_multipart(v, false).each { |subkey, subvalue|
|
|
652
|
-
flattened_params["#{k}[]#{subkey}"] = subvalue
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
when Hash
|
|
656
|
-
build_multipart(value, false).each { |subkey, subvalue|
|
|
657
|
-
flattened_params[k + subkey] = subvalue
|
|
658
|
-
}
|
|
659
|
-
else
|
|
660
|
-
flattened_params[k] = value
|
|
661
|
-
end
|
|
662
|
-
end
|
|
663
|
-
|
|
664
|
-
if first
|
|
665
|
-
flattened_params.map { |name, file|
|
|
666
|
-
if file.respond_to?(:original_filename)
|
|
667
|
-
::File.open(file.path, "rb") do |f|
|
|
668
|
-
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
|
669
|
-
<<-EOF
|
|
670
|
-
--#{MULTIPART_BOUNDARY}\r
|
|
671
|
-
Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
|
|
672
|
-
Content-Type: #{file.content_type}\r
|
|
673
|
-
Content-Length: #{::File.stat(file.path).size}\r
|
|
674
|
-
\r
|
|
675
|
-
#{f.read}\r
|
|
676
|
-
EOF
|
|
677
|
-
end
|
|
678
|
-
else
|
|
679
|
-
<<-EOF
|
|
680
|
-
--#{MULTIPART_BOUNDARY}\r
|
|
681
|
-
Content-Disposition: form-data; name="#{name}"\r
|
|
682
|
-
\r
|
|
683
|
-
#{file}\r
|
|
684
|
-
EOF
|
|
685
|
-
end
|
|
686
|
-
}.join + "--#{MULTIPART_BOUNDARY}--\r"
|
|
687
|
-
else
|
|
688
|
-
flattened_params
|
|
689
|
-
end
|
|
690
|
-
end
|
|
690
|
+
::File.join(*clean)
|
|
691
691
|
end
|
|
692
|
+
module_function :clean_path_info
|
|
693
|
+
|
|
692
694
|
end
|
|
693
695
|
end
|