rack 1.6.13 → 2.1.4.3
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 +92 -0
- data/{COPYING → MIT-LICENSE} +4 -2
- data/README.rdoc +105 -141
- data/Rakefile +27 -28
- data/SPEC +6 -7
- data/bin/rackup +1 -0
- data/contrib/rack_logo.svg +164 -111
- data/example/lobster.ru +2 -0
- data/example/protectedlobster.rb +4 -2
- data/example/protectedlobster.ru +3 -1
- data/lib/rack/auth/abstract/handler.rb +3 -1
- data/lib/rack/auth/abstract/request.rb +7 -1
- 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 +5 -4
- data/lib/rack/auth/digest/request.rb +3 -1
- data/lib/rack/body_proxy.rb +11 -9
- data/lib/rack/builder.rb +42 -18
- data/lib/rack/cascade.rb +6 -5
- data/lib/rack/chunked.rb +33 -10
- data/lib/rack/{commonlogger.rb → common_logger.rb} +14 -10
- data/lib/rack/{conditionalget.rb → conditional_get.rb} +3 -1
- data/lib/rack/config.rb +2 -0
- data/lib/rack/content_length.rb +5 -3
- data/lib/rack/content_type.rb +3 -1
- data/lib/rack/core_ext/regexp.rb +14 -0
- data/lib/rack/deflater.rb +33 -53
- data/lib/rack/directory.rb +75 -60
- data/lib/rack/etag.rb +8 -5
- data/lib/rack/events.rb +156 -0
- data/lib/rack/file.rb +4 -149
- data/lib/rack/files.rb +178 -0
- data/lib/rack/handler/cgi.rb +18 -17
- data/lib/rack/handler/fastcgi.rb +17 -16
- data/lib/rack/handler/lsws.rb +14 -12
- data/lib/rack/handler/scgi.rb +22 -19
- data/lib/rack/handler/thin.rb +6 -1
- data/lib/rack/handler/webrick.rb +28 -28
- data/lib/rack/handler.rb +9 -26
- data/lib/rack/head.rb +17 -17
- data/lib/rack/lint.rb +55 -52
- data/lib/rack/lobster.rb +8 -6
- data/lib/rack/lock.rb +17 -10
- data/lib/rack/logger.rb +4 -2
- data/lib/rack/media_type.rb +43 -0
- data/lib/rack/{methodoverride.rb → method_override.rb} +10 -8
- data/lib/rack/mime.rb +27 -6
- data/lib/rack/mock.rb +101 -60
- data/lib/rack/multipart/generator.rb +11 -12
- data/lib/rack/multipart/parser.rb +292 -161
- data/lib/rack/multipart/uploaded_file.rb +3 -2
- data/lib/rack/multipart.rb +38 -8
- data/lib/rack/{nulllogger.rb → null_logger.rb} +3 -1
- data/lib/rack/query_parser.rb +218 -0
- data/lib/rack/recursive.rb +11 -9
- data/lib/rack/reloader.rb +10 -4
- data/lib/rack/request.rb +447 -305
- data/lib/rack/response.rb +196 -83
- data/lib/rack/rewindable_input.rb +5 -14
- data/lib/rack/runtime.rb +12 -18
- data/lib/rack/sendfile.rb +19 -14
- data/lib/rack/server.rb +118 -41
- data/lib/rack/session/abstract/id.rb +139 -94
- data/lib/rack/session/cookie.rb +34 -26
- data/lib/rack/session/memcache.rb +4 -93
- data/lib/rack/session/pool.rb +12 -10
- data/lib/rack/show_exceptions.rb +392 -0
- data/lib/rack/{showstatus.rb → show_status.rb} +7 -5
- data/lib/rack/static.rb +41 -11
- data/lib/rack/tempfile_reaper.rb +4 -2
- data/lib/rack/urlmap.rb +25 -15
- data/lib/rack/utils.rb +203 -277
- data/lib/rack.rb +76 -24
- data/rack.gemspec +25 -14
- metadata +62 -183
- data/HISTORY.md +0 -375
- data/KNOWN-ISSUES +0 -44
- data/lib/rack/backports/uri/common_18.rb +0 -56
- data/lib/rack/backports/uri/common_192.rb +0 -52
- data/lib/rack/backports/uri/common_193.rb +0 -29
- data/lib/rack/handler/evented_mongrel.rb +0 -8
- data/lib/rack/handler/mongrel.rb +0 -106
- data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
- data/lib/rack/showexceptions.rb +0 -387
- data/lib/rack/utils/okjson.rb +0 -600
- 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 -8
- data/test/cgi/test.ru +0 -5
- data/test/gemloader.rb +0 -10
- 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_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_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/semicolon +0 -6
- data/test/multipart/text +0 -15
- data/test/multipart/three_files_three_fields +0 -31
- 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 -81
- data/test/spec_auth_digest.rb +0 -259
- data/test/spec_body_proxy.rb +0 -85
- data/test/spec_builder.rb +0 -223
- data/test/spec_cascade.rb +0 -61
- data/test/spec_cgi.rb +0 -102
- data/test/spec_chunked.rb +0 -101
- data/test/spec_commonlogger.rb +0 -93
- data/test/spec_conditionalget.rb +0 -102
- data/test/spec_config.rb +0 -22
- data/test/spec_content_length.rb +0 -85
- data/test/spec_content_type.rb +0 -45
- data/test/spec_deflater.rb +0 -339
- data/test/spec_directory.rb +0 -88
- data/test/spec_etag.rb +0 -107
- data/test/spec_fastcgi.rb +0 -107
- data/test/spec_file.rb +0 -221
- data/test/spec_handler.rb +0 -72
- data/test/spec_head.rb +0 -45
- data/test/spec_lint.rb +0 -550
- data/test/spec_lobster.rb +0 -58
- data/test/spec_lock.rb +0 -164
- data/test/spec_logger.rb +0 -23
- data/test/spec_methodoverride.rb +0 -111
- data/test/spec_mime.rb +0 -51
- data/test/spec_mock.rb +0 -297
- data/test/spec_mongrel.rb +0 -182
- data/test/spec_multipart.rb +0 -600
- data/test/spec_nulllogger.rb +0 -20
- data/test/spec_recursive.rb +0 -72
- data/test/spec_request.rb +0 -1232
- data/test/spec_response.rb +0 -407
- data/test/spec_rewindable_input.rb +0 -118
- data/test/spec_runtime.rb +0 -49
- data/test/spec_sendfile.rb +0 -130
- data/test/spec_server.rb +0 -167
- data/test/spec_session_abstract_id.rb +0 -53
- data/test/spec_session_cookie.rb +0 -410
- data/test/spec_session_memcache.rb +0 -358
- data/test/spec_session_persisted_secure_secure_session_hash.rb +0 -73
- data/test/spec_session_pool.rb +0 -246
- data/test/spec_showexceptions.rb +0 -98
- data/test/spec_showstatus.rb +0 -103
- data/test/spec_static.rb +0 -145
- data/test/spec_tempfile_reaper.rb +0 -63
- data/test/spec_thin.rb +0 -91
- data/test/spec_urlmap.rb +0 -236
- data/test/spec_utils.rb +0 -647
- data/test/spec_version.rb +0 -17
- data/test/spec_webrick.rb +0 -184
- data/test/static/another/index.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
data/lib/rack/request.rb
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rack/utils'
|
|
4
|
+
require 'rack/media_type'
|
|
5
|
+
|
|
6
|
+
require_relative 'core_ext/regexp'
|
|
2
7
|
|
|
3
8
|
module Rack
|
|
4
9
|
# Rack::Request provides a convenient interface to a Rack
|
|
@@ -10,371 +15,462 @@ module Rack
|
|
|
10
15
|
# req.params["data"]
|
|
11
16
|
|
|
12
17
|
class Request
|
|
13
|
-
|
|
14
|
-
attr_reader :env
|
|
15
|
-
|
|
16
|
-
SCHEME_WHITELIST = %w(https http).freeze
|
|
18
|
+
using ::Rack::RegexpExtensions
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
class << self
|
|
21
|
+
attr_accessor :ip_filter
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
self.ip_filter = lambda { |ip| /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i.match?(ip) }
|
|
25
|
+
ALLOWED_SCHEMES = %w(https http).freeze
|
|
26
|
+
SCHEME_WHITELIST = ALLOWED_SCHEMES
|
|
27
|
+
if Object.respond_to?(:deprecate_constant)
|
|
28
|
+
deprecate_constant :SCHEME_WHITELIST
|
|
29
|
+
end
|
|
28
30
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
def initialize(env)
|
|
32
|
+
@params = nil
|
|
33
|
+
super(env)
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
def logger; @env['rack.logger'] end
|
|
37
|
-
|
|
38
|
-
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
|
39
|
-
# without any media type parameters. e.g., when CONTENT_TYPE is
|
|
40
|
-
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
|
41
|
-
#
|
|
42
|
-
# For more information on the use of media types in HTTP, see:
|
|
43
|
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
|
44
|
-
def media_type
|
|
45
|
-
content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
|
|
36
|
+
def params
|
|
37
|
+
@params ||= super
|
|
46
38
|
end
|
|
47
39
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# this method responds with the following Hash:
|
|
52
|
-
# { 'charset' => 'utf-8' }
|
|
53
|
-
def media_type_params
|
|
54
|
-
return {} if content_type.nil?
|
|
55
|
-
Hash[*content_type.split(/\s*[;,]\s*/)[1..-1].
|
|
56
|
-
collect { |s| s.split('=', 2) }.
|
|
57
|
-
map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten]
|
|
40
|
+
def update_param(k, v)
|
|
41
|
+
super
|
|
42
|
+
@params = nil
|
|
58
43
|
end
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def content_charset
|
|
65
|
-
media_type_params['charset']
|
|
45
|
+
def delete_param(k)
|
|
46
|
+
v = super
|
|
47
|
+
@params = nil
|
|
48
|
+
v
|
|
66
49
|
end
|
|
67
50
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
else
|
|
76
|
-
@env["rack.url_scheme"]
|
|
51
|
+
module Env
|
|
52
|
+
# The environment of the request.
|
|
53
|
+
attr_reader :env
|
|
54
|
+
|
|
55
|
+
def initialize(env)
|
|
56
|
+
@env = env
|
|
57
|
+
super()
|
|
77
58
|
end
|
|
78
|
-
end
|
|
79
59
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
60
|
+
# Predicate method to test to see if `name` has been set as request
|
|
61
|
+
# specific data
|
|
62
|
+
def has_header?(name)
|
|
63
|
+
@env.key? name
|
|
64
|
+
end
|
|
83
65
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
else
|
|
88
|
-
@env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}"
|
|
66
|
+
# Get a request specific value for `name`.
|
|
67
|
+
def get_header(name)
|
|
68
|
+
@env[name]
|
|
89
69
|
end
|
|
90
|
-
end
|
|
91
70
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
port.to_i
|
|
97
|
-
elsif @env.has_key?("HTTP_X_FORWARDED_HOST")
|
|
98
|
-
DEFAULT_PORTS[scheme]
|
|
99
|
-
elsif @env.has_key?("HTTP_X_FORWARDED_PROTO")
|
|
100
|
-
DEFAULT_PORTS[@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]]
|
|
101
|
-
else
|
|
102
|
-
@env["SERVER_PORT"].to_i
|
|
71
|
+
# If a block is given, it yields to the block if the value hasn't been set
|
|
72
|
+
# on the request.
|
|
73
|
+
def fetch_header(name, &block)
|
|
74
|
+
@env.fetch(name, &block)
|
|
103
75
|
end
|
|
104
|
-
end
|
|
105
76
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
77
|
+
# Loops through each key / value pair in the request specific data.
|
|
78
|
+
def each_header(&block)
|
|
79
|
+
@env.each(&block)
|
|
80
|
+
end
|
|
110
81
|
|
|
111
|
-
|
|
112
|
-
|
|
82
|
+
# Set a request specific value for `name` to `v`
|
|
83
|
+
def set_header(name, v)
|
|
84
|
+
@env[name] = v
|
|
85
|
+
end
|
|
113
86
|
|
|
87
|
+
# Add a header that may have multiple values.
|
|
88
|
+
#
|
|
89
|
+
# Example:
|
|
90
|
+
# request.add_header 'Accept', 'image/png'
|
|
91
|
+
# request.add_header 'Accept', '*/*'
|
|
92
|
+
#
|
|
93
|
+
# assert_equal 'image/png,*/*', request.get_header('Accept')
|
|
94
|
+
#
|
|
95
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
|
96
|
+
def add_header key, v
|
|
97
|
+
if v.nil?
|
|
98
|
+
get_header key
|
|
99
|
+
elsif has_header? key
|
|
100
|
+
set_header key, "#{get_header key},#{v}"
|
|
101
|
+
else
|
|
102
|
+
set_header key, v
|
|
103
|
+
end
|
|
104
|
+
end
|
|
114
105
|
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
# Delete a request specific value for `name`.
|
|
107
|
+
def delete_header(name)
|
|
108
|
+
@env.delete name
|
|
109
|
+
end
|
|
117
110
|
|
|
118
|
-
|
|
119
|
-
|
|
111
|
+
def initialize_copy(other)
|
|
112
|
+
@env = other.env.dup
|
|
113
|
+
end
|
|
114
|
+
end
|
|
120
115
|
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
module Helpers
|
|
117
|
+
# The set of form-data media-types. Requests that do not indicate
|
|
118
|
+
# one of the media types present in this list will not be eligible
|
|
119
|
+
# for form-data / param parsing.
|
|
120
|
+
FORM_DATA_MEDIA_TYPES = [
|
|
121
|
+
'application/x-www-form-urlencoded',
|
|
122
|
+
'multipart/form-data'
|
|
123
|
+
]
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
# The set of media-types. Requests that do not indicate
|
|
126
|
+
# one of the media types present in this list will not be eligible
|
|
127
|
+
# for param parsing like soap attachments or generic multiparts
|
|
128
|
+
PARSEABLE_DATA_MEDIA_TYPES = [
|
|
129
|
+
'multipart/related',
|
|
130
|
+
'multipart/mixed'
|
|
131
|
+
]
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
# Default ports depending on scheme. Used to decide whether or not
|
|
134
|
+
# to include the port in a generated URI.
|
|
135
|
+
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
|
136
|
+
|
|
137
|
+
HTTP_X_FORWARDED_SCHEME = 'HTTP_X_FORWARDED_SCHEME'
|
|
138
|
+
HTTP_X_FORWARDED_PROTO = 'HTTP_X_FORWARDED_PROTO'
|
|
139
|
+
HTTP_X_FORWARDED_HOST = 'HTTP_X_FORWARDED_HOST'
|
|
140
|
+
HTTP_X_FORWARDED_PORT = 'HTTP_X_FORWARDED_PORT'
|
|
141
|
+
HTTP_X_FORWARDED_SSL = 'HTTP_X_FORWARDED_SSL'
|
|
142
|
+
|
|
143
|
+
def body; get_header(RACK_INPUT) end
|
|
144
|
+
def script_name; get_header(SCRIPT_NAME).to_s end
|
|
145
|
+
def script_name=(s); set_header(SCRIPT_NAME, s.to_s) end
|
|
146
|
+
|
|
147
|
+
def path_info; get_header(PATH_INFO).to_s end
|
|
148
|
+
def path_info=(s); set_header(PATH_INFO, s.to_s) end
|
|
149
|
+
|
|
150
|
+
def request_method; get_header(REQUEST_METHOD) end
|
|
151
|
+
def query_string; get_header(QUERY_STRING).to_s end
|
|
152
|
+
def content_length; get_header('CONTENT_LENGTH') end
|
|
153
|
+
def logger; get_header(RACK_LOGGER) end
|
|
154
|
+
def user_agent; get_header('HTTP_USER_AGENT') end
|
|
155
|
+
def multithread?; get_header(RACK_MULTITHREAD) end
|
|
156
|
+
|
|
157
|
+
# the referer of the client
|
|
158
|
+
def referer; get_header('HTTP_REFERER') end
|
|
159
|
+
alias referrer referer
|
|
160
|
+
|
|
161
|
+
def session
|
|
162
|
+
fetch_header(RACK_SESSION) do |k|
|
|
163
|
+
set_header RACK_SESSION, default_session
|
|
164
|
+
end
|
|
165
|
+
end
|
|
129
166
|
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
def session_options
|
|
168
|
+
fetch_header(RACK_SESSION_OPTIONS) do |k|
|
|
169
|
+
set_header RACK_SESSION_OPTIONS, {}
|
|
170
|
+
end
|
|
171
|
+
end
|
|
132
172
|
|
|
133
|
-
|
|
134
|
-
|
|
173
|
+
# Checks the HTTP request method (or verb) to see if it was of type DELETE
|
|
174
|
+
def delete?; request_method == DELETE end
|
|
135
175
|
|
|
136
|
-
|
|
137
|
-
|
|
176
|
+
# Checks the HTTP request method (or verb) to see if it was of type GET
|
|
177
|
+
def get?; request_method == GET end
|
|
138
178
|
|
|
139
|
-
|
|
140
|
-
|
|
179
|
+
# Checks the HTTP request method (or verb) to see if it was of type HEAD
|
|
180
|
+
def head?; request_method == HEAD end
|
|
141
181
|
|
|
142
|
-
|
|
143
|
-
|
|
182
|
+
# Checks the HTTP request method (or verb) to see if it was of type OPTIONS
|
|
183
|
+
def options?; request_method == OPTIONS end
|
|
144
184
|
|
|
185
|
+
# Checks the HTTP request method (or verb) to see if it was of type LINK
|
|
186
|
+
def link?; request_method == LINK end
|
|
145
187
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
# for form-data / param parsing.
|
|
149
|
-
FORM_DATA_MEDIA_TYPES = [
|
|
150
|
-
'application/x-www-form-urlencoded',
|
|
151
|
-
'multipart/form-data'
|
|
152
|
-
]
|
|
188
|
+
# Checks the HTTP request method (or verb) to see if it was of type PATCH
|
|
189
|
+
def patch?; request_method == PATCH end
|
|
153
190
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
# for param parsing like soap attachments or generic multiparts
|
|
157
|
-
PARSEABLE_DATA_MEDIA_TYPES = [
|
|
158
|
-
'multipart/related',
|
|
159
|
-
'multipart/mixed'
|
|
160
|
-
]
|
|
191
|
+
# Checks the HTTP request method (or verb) to see if it was of type POST
|
|
192
|
+
def post?; request_method == POST end
|
|
161
193
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 }
|
|
194
|
+
# Checks the HTTP request method (or verb) to see if it was of type PUT
|
|
195
|
+
def put?; request_method == PUT end
|
|
165
196
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
|
169
|
-
# list of form-data media types can be modified through the
|
|
170
|
-
# +FORM_DATA_MEDIA_TYPES+ array.
|
|
171
|
-
#
|
|
172
|
-
# A request body is also assumed to contain form-data when no
|
|
173
|
-
# Content-Type header is provided and the request_method is POST.
|
|
174
|
-
def form_data?
|
|
175
|
-
type = media_type
|
|
176
|
-
meth = env["rack.methodoverride.original_method"] || env[REQUEST_METHOD]
|
|
177
|
-
(meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
|
178
|
-
end
|
|
197
|
+
# Checks the HTTP request method (or verb) to see if it was of type TRACE
|
|
198
|
+
def trace?; request_method == TRACE end
|
|
179
199
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def parseable_data?
|
|
183
|
-
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
|
|
184
|
-
end
|
|
200
|
+
# Checks the HTTP request method (or verb) to see if it was of type UNLINK
|
|
201
|
+
def unlink?; request_method == UNLINK end
|
|
185
202
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
203
|
+
def scheme
|
|
204
|
+
if get_header(HTTPS) == 'on'
|
|
205
|
+
'https'
|
|
206
|
+
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
|
|
207
|
+
'https'
|
|
208
|
+
elsif forwarded_scheme
|
|
209
|
+
forwarded_scheme
|
|
210
|
+
else
|
|
211
|
+
get_header(RACK_URL_SCHEME)
|
|
212
|
+
end
|
|
194
213
|
end
|
|
195
|
-
end
|
|
196
214
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
elsif @env["rack.request.form_input"].equal? @env["rack.input"]
|
|
205
|
-
@env["rack.request.form_hash"]
|
|
206
|
-
elsif form_data? || parseable_data?
|
|
207
|
-
unless @env["rack.request.form_hash"] = parse_multipart(env)
|
|
208
|
-
form_vars = @env["rack.input"].read
|
|
209
|
-
|
|
210
|
-
# Fix for Safari Ajax postings that always append \0
|
|
211
|
-
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
212
|
-
form_vars.slice!(-1) if form_vars[-1] == ?\0
|
|
213
|
-
|
|
214
|
-
@env["rack.request.form_vars"] = form_vars
|
|
215
|
-
@env["rack.request.form_hash"] = parse_query({ :query => form_vars, :separator => '&' })
|
|
216
|
-
|
|
217
|
-
@env["rack.input"].rewind
|
|
215
|
+
def authority
|
|
216
|
+
get_header(SERVER_NAME) + ':' + get_header(SERVER_PORT)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def cookies
|
|
220
|
+
hash = fetch_header(RACK_REQUEST_COOKIE_HASH) do |k|
|
|
221
|
+
set_header(k, {})
|
|
218
222
|
end
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
string = get_header HTTP_COOKIE
|
|
224
|
+
|
|
225
|
+
return hash if string == get_header(RACK_REQUEST_COOKIE_STRING)
|
|
226
|
+
hash.replace Utils.parse_cookies_header string
|
|
227
|
+
set_header(RACK_REQUEST_COOKIE_STRING, string)
|
|
228
|
+
hash
|
|
223
229
|
end
|
|
224
|
-
end
|
|
225
230
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
@params ||= self.GET.merge(self.POST)
|
|
231
|
-
rescue EOFError
|
|
232
|
-
self.GET.dup
|
|
233
|
-
end
|
|
231
|
+
def content_type
|
|
232
|
+
content_type = get_header('CONTENT_TYPE')
|
|
233
|
+
content_type.nil? || content_type.empty? ? nil : content_type
|
|
234
|
+
end
|
|
234
235
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
# The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
|
238
|
-
#
|
|
239
|
-
# env['rack.input'] is not touched.
|
|
240
|
-
def update_param(k, v)
|
|
241
|
-
found = false
|
|
242
|
-
if self.GET.has_key?(k)
|
|
243
|
-
found = true
|
|
244
|
-
self.GET[k] = v
|
|
236
|
+
def xhr?
|
|
237
|
+
get_header("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
|
|
245
238
|
end
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
239
|
+
|
|
240
|
+
def host_with_port
|
|
241
|
+
if forwarded = get_header(HTTP_X_FORWARDED_HOST)
|
|
242
|
+
forwarded.split(/,\s?/).last
|
|
243
|
+
else
|
|
244
|
+
get_header(HTTP_HOST) || "#{get_header(SERVER_NAME) || get_header(SERVER_ADDR)}:#{get_header(SERVER_PORT)}"
|
|
245
|
+
end
|
|
249
246
|
end
|
|
250
|
-
|
|
251
|
-
|
|
247
|
+
|
|
248
|
+
def host
|
|
249
|
+
# Remove port number.
|
|
250
|
+
h = host_with_port
|
|
251
|
+
if colon_index = h.index(":")
|
|
252
|
+
h[0, colon_index]
|
|
253
|
+
else
|
|
254
|
+
h
|
|
255
|
+
end
|
|
252
256
|
end
|
|
253
|
-
@params = nil
|
|
254
|
-
nil
|
|
255
|
-
end
|
|
256
257
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
258
|
+
def port
|
|
259
|
+
if port = extract_port(host_with_port)
|
|
260
|
+
port.to_i
|
|
261
|
+
elsif port = get_header(HTTP_X_FORWARDED_PORT)
|
|
262
|
+
port.to_i
|
|
263
|
+
elsif has_header?(HTTP_X_FORWARDED_HOST)
|
|
264
|
+
DEFAULT_PORTS[scheme]
|
|
265
|
+
elsif has_header?(HTTP_X_FORWARDED_PROTO)
|
|
266
|
+
DEFAULT_PORTS[extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO))]
|
|
267
|
+
else
|
|
268
|
+
get_header(SERVER_PORT).to_i
|
|
269
|
+
end
|
|
270
|
+
end
|
|
267
271
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
end
|
|
272
|
+
def ssl?
|
|
273
|
+
scheme == 'https'
|
|
274
|
+
end
|
|
272
275
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
def []=(key, value)
|
|
277
|
-
params[key.to_s] = value
|
|
278
|
-
end
|
|
276
|
+
def ip
|
|
277
|
+
remote_addrs = split_ip_addresses(get_header('REMOTE_ADDR'))
|
|
278
|
+
remote_addrs = reject_trusted_ip_addresses(remote_addrs)
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
def values_at(*keys)
|
|
282
|
-
keys.map{|key| params[key] }
|
|
283
|
-
end
|
|
280
|
+
return remote_addrs.first if remote_addrs.any?
|
|
284
281
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
@env['HTTP_REFERER']
|
|
288
|
-
end
|
|
289
|
-
alias referrer referer
|
|
282
|
+
forwarded_ips = split_ip_addresses(get_header('HTTP_X_FORWARDED_FOR'))
|
|
283
|
+
.map { |ip| strip_port(ip) }
|
|
290
284
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
end
|
|
285
|
+
return reject_trusted_ip_addresses(forwarded_ips).last || forwarded_ips.first || get_header("REMOTE_ADDR")
|
|
286
|
+
end
|
|
294
287
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
# the Cookie header such that those with more specific Path attributes
|
|
305
|
-
# precede those with less specific. Ordering with respect to other
|
|
306
|
-
# attributes (e.g., Domain) is unspecified.
|
|
307
|
-
cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
|
|
308
|
-
cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
|
|
309
|
-
@env["rack.request.cookie_string"] = string
|
|
310
|
-
hash
|
|
311
|
-
end
|
|
288
|
+
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
|
289
|
+
# without any media type parameters. e.g., when CONTENT_TYPE is
|
|
290
|
+
# "text/plain;charset=utf-8", the media-type is "text/plain".
|
|
291
|
+
#
|
|
292
|
+
# For more information on the use of media types in HTTP, see:
|
|
293
|
+
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
|
294
|
+
def media_type
|
|
295
|
+
MediaType.type(content_type)
|
|
296
|
+
end
|
|
312
297
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
298
|
+
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
|
299
|
+
# an empty Hash if no CONTENT_TYPE or media-type parameters were
|
|
300
|
+
# provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8",
|
|
301
|
+
# this method responds with the following Hash:
|
|
302
|
+
# { 'charset' => 'utf-8' }
|
|
303
|
+
def media_type_params
|
|
304
|
+
MediaType.params(content_type)
|
|
305
|
+
end
|
|
316
306
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
307
|
+
# The character set of the request body if a "charset" media type
|
|
308
|
+
# parameter was given, or nil if no "charset" was specified. Note
|
|
309
|
+
# that, per RFC2616, text/* media types that specify no explicit
|
|
310
|
+
# charset are to be considered ISO-8859-1.
|
|
311
|
+
def content_charset
|
|
312
|
+
media_type_params['charset']
|
|
313
|
+
end
|
|
322
314
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
315
|
+
# Determine whether the request body contains form-data by checking
|
|
316
|
+
# the request Content-Type for one of the media-types:
|
|
317
|
+
# "application/x-www-form-urlencoded" or "multipart/form-data". The
|
|
318
|
+
# list of form-data media types can be modified through the
|
|
319
|
+
# +FORM_DATA_MEDIA_TYPES+ array.
|
|
320
|
+
#
|
|
321
|
+
# A request body is also assumed to contain form-data when no
|
|
322
|
+
# Content-Type header is provided and the request_method is POST.
|
|
323
|
+
def form_data?
|
|
324
|
+
type = media_type
|
|
325
|
+
meth = get_header(RACK_METHODOVERRIDE_ORIGINAL_METHOD) || get_header(REQUEST_METHOD)
|
|
326
|
+
(meth == POST && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type)
|
|
327
|
+
end
|
|
327
328
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
329
|
+
# Determine whether the request body contains data by checking
|
|
330
|
+
# the request media_type against registered parse-data media-types
|
|
331
|
+
def parseable_data?
|
|
332
|
+
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
|
|
333
|
+
end
|
|
331
334
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
# Returns the data received in the query string.
|
|
336
|
+
def GET
|
|
337
|
+
if get_header(RACK_REQUEST_QUERY_STRING) == query_string
|
|
338
|
+
get_header(RACK_REQUEST_QUERY_HASH)
|
|
339
|
+
else
|
|
340
|
+
query_hash = parse_query(query_string, '&;')
|
|
341
|
+
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
|
342
|
+
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
335
345
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
346
|
+
# Returns the data received in the request body.
|
|
347
|
+
#
|
|
348
|
+
# This method support both application/x-www-form-urlencoded and
|
|
349
|
+
# multipart/form-data.
|
|
350
|
+
def POST
|
|
351
|
+
if get_header(RACK_INPUT).nil?
|
|
352
|
+
raise "Missing rack.input"
|
|
353
|
+
elsif get_header(RACK_REQUEST_FORM_INPUT) == get_header(RACK_INPUT)
|
|
354
|
+
get_header(RACK_REQUEST_FORM_HASH)
|
|
355
|
+
elsif form_data? || parseable_data?
|
|
356
|
+
unless set_header(RACK_REQUEST_FORM_HASH, parse_multipart)
|
|
357
|
+
form_vars = get_header(RACK_INPUT).read
|
|
358
|
+
|
|
359
|
+
# Fix for Safari Ajax postings that always append \0
|
|
360
|
+
# form_vars.sub!(/\0\z/, '') # performance replacement:
|
|
361
|
+
form_vars.slice!(-1) if form_vars.end_with?("\0")
|
|
362
|
+
|
|
363
|
+
set_header RACK_REQUEST_FORM_VARS, form_vars
|
|
364
|
+
set_header RACK_REQUEST_FORM_HASH, parse_query(form_vars, '&')
|
|
365
|
+
|
|
366
|
+
get_header(RACK_INPUT).rewind
|
|
367
|
+
end
|
|
368
|
+
set_header RACK_REQUEST_FORM_INPUT, get_header(RACK_INPUT)
|
|
369
|
+
get_header RACK_REQUEST_FORM_HASH
|
|
370
|
+
else
|
|
371
|
+
{}
|
|
372
|
+
end
|
|
373
|
+
end
|
|
339
374
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
375
|
+
# The union of GET and POST data.
|
|
376
|
+
#
|
|
377
|
+
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
|
378
|
+
def params
|
|
379
|
+
self.GET.merge(self.POST)
|
|
380
|
+
rescue EOFError
|
|
381
|
+
self.GET.dup
|
|
382
|
+
end
|
|
343
383
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
384
|
+
# Destructively update a parameter, whether it's in GET and/or POST. Returns nil.
|
|
385
|
+
#
|
|
386
|
+
# The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET.
|
|
387
|
+
#
|
|
388
|
+
# <tt>env['rack.input']</tt> is not touched.
|
|
389
|
+
def update_param(k, v)
|
|
390
|
+
found = false
|
|
391
|
+
if self.GET.has_key?(k)
|
|
392
|
+
found = true
|
|
393
|
+
self.GET[k] = v
|
|
394
|
+
end
|
|
395
|
+
if self.POST.has_key?(k)
|
|
396
|
+
found = true
|
|
397
|
+
self.POST[k] = v
|
|
398
|
+
end
|
|
399
|
+
unless found
|
|
400
|
+
self.GET[k] = v
|
|
401
|
+
end
|
|
402
|
+
end
|
|
347
403
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
404
|
+
# Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter.
|
|
405
|
+
#
|
|
406
|
+
# If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works.
|
|
407
|
+
#
|
|
408
|
+
# <tt>env['rack.input']</tt> is not touched.
|
|
409
|
+
def delete_param(k)
|
|
410
|
+
post_value, get_value = self.POST.delete(k), self.GET.delete(k)
|
|
411
|
+
post_value || get_value
|
|
412
|
+
end
|
|
351
413
|
|
|
352
|
-
|
|
414
|
+
def base_url
|
|
415
|
+
url = "#{scheme}://#{host}"
|
|
416
|
+
url = "#{url}:#{port}" if port != DEFAULT_PORTS[scheme]
|
|
417
|
+
url
|
|
418
|
+
end
|
|
353
419
|
|
|
354
|
-
|
|
420
|
+
# Tries to return a remake of the original request URL as a string.
|
|
421
|
+
def url
|
|
422
|
+
base_url + fullpath
|
|
423
|
+
end
|
|
355
424
|
|
|
356
|
-
|
|
357
|
-
|
|
425
|
+
def path
|
|
426
|
+
script_name + path_info
|
|
427
|
+
end
|
|
358
428
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
|
|
429
|
+
def fullpath
|
|
430
|
+
query_string.empty? ? path : "#{path}?#{query_string}"
|
|
362
431
|
end
|
|
363
432
|
|
|
364
|
-
def
|
|
365
|
-
|
|
433
|
+
def accept_encoding
|
|
434
|
+
parse_http_accept_header(get_header("HTTP_ACCEPT_ENCODING"))
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def accept_language
|
|
438
|
+
parse_http_accept_header(get_header("HTTP_ACCEPT_LANGUAGE"))
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def trusted_proxy?(ip)
|
|
442
|
+
Rack::Request.ip_filter.call(ip)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# shortcut for <tt>request.params[key]</tt>
|
|
446
|
+
def [](key)
|
|
447
|
+
if $VERBOSE
|
|
448
|
+
warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
params[key.to_s]
|
|
366
452
|
end
|
|
367
453
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
454
|
+
# shortcut for <tt>request.params[key] = value</tt>
|
|
455
|
+
#
|
|
456
|
+
# Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
|
|
457
|
+
def []=(key, value)
|
|
458
|
+
if $VERBOSE
|
|
459
|
+
warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
params[key.to_s] = value
|
|
372
463
|
end
|
|
373
464
|
|
|
374
|
-
|
|
375
|
-
|
|
465
|
+
# like Hash#values_at
|
|
466
|
+
def values_at(*keys)
|
|
467
|
+
keys.map { |key| params[key] }
|
|
376
468
|
end
|
|
377
469
|
|
|
470
|
+
private
|
|
471
|
+
|
|
472
|
+
def default_session; {}; end
|
|
473
|
+
|
|
378
474
|
def parse_http_accept_header(header)
|
|
379
475
|
header.to_s.split(/\s*,\s*/).map do |part|
|
|
380
476
|
attribute, parameters = part.split(/\s*;\s*/, 2)
|
|
@@ -386,26 +482,72 @@ module Rack
|
|
|
386
482
|
end
|
|
387
483
|
end
|
|
388
484
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if s[0] == ?" && s[-1] == ?"
|
|
392
|
-
s[1..-2]
|
|
393
|
-
else
|
|
394
|
-
s
|
|
485
|
+
def query_parser
|
|
486
|
+
Utils.default_query_parser
|
|
395
487
|
end
|
|
396
|
-
end
|
|
397
488
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
489
|
+
def parse_query(qs, d = '&')
|
|
490
|
+
query_parser.parse_nested_query(qs, d)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def parse_multipart
|
|
494
|
+
Rack::Multipart.extract_multipart(self, query_parser)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def split_ip_addresses(ip_addresses)
|
|
498
|
+
ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : []
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def strip_port(ip_address)
|
|
502
|
+
# IPv6 format with optional port: "[2001:db8:cafe::17]:47011"
|
|
503
|
+
# returns: "2001:db8:cafe::17"
|
|
504
|
+
sep_start = ip_address.index('[')
|
|
505
|
+
sep_end = ip_address.index(']')
|
|
506
|
+
if (sep_start && sep_end)
|
|
507
|
+
return ip_address[sep_start + 1, sep_end - 1]
|
|
508
|
+
end
|
|
403
509
|
|
|
404
|
-
|
|
405
|
-
|
|
510
|
+
# IPv4 format with optional port: "192.0.2.43:47011"
|
|
511
|
+
# returns: "192.0.2.43"
|
|
512
|
+
sep = ip_address.index(':')
|
|
513
|
+
if (sep && ip_address.count(':') == 1)
|
|
514
|
+
return ip_address[0, sep]
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
ip_address
|
|
406
518
|
end
|
|
407
519
|
|
|
408
|
-
|
|
520
|
+
def reject_trusted_ip_addresses(ip_addresses)
|
|
521
|
+
ip_addresses.reject { |ip| trusted_proxy?(ip) }
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def forwarded_scheme
|
|
525
|
+
allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
|
|
526
|
+
allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def allowed_scheme(header)
|
|
530
|
+
header if ALLOWED_SCHEMES.include?(header)
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def extract_proto_header(header)
|
|
534
|
+
if header
|
|
535
|
+
if (comma_index = header.index(','))
|
|
536
|
+
header[0, comma_index]
|
|
537
|
+
else
|
|
538
|
+
header
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
def extract_port(uri)
|
|
544
|
+
if (colon_index = uri.index(':'))
|
|
545
|
+
uri[colon_index + 1, uri.length]
|
|
546
|
+
end
|
|
547
|
+
end
|
|
409
548
|
end
|
|
549
|
+
|
|
550
|
+
include Env
|
|
551
|
+
include Helpers
|
|
410
552
|
end
|
|
411
553
|
end
|