actionpack 5.0.0.1 → 5.0.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +195 -0
- data/README.rdoc +1 -1
- data/lib/abstract_controller/callbacks.rb +3 -1
- data/lib/action_controller/metal/data_streaming.rb +1 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +7 -1
- data/lib/action_controller/metal/force_ssl.rb +1 -1
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/live.rb +6 -1
- data/lib/action_controller/metal/renderers.rb +0 -2
- data/lib/action_controller/metal/rendering.rb +1 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +3 -3
- data/lib/action_controller/metal/strong_parameters.rb +53 -15
- data/lib/action_controller/test_case.rb +39 -21
- data/lib/action_dispatch.rb +1 -0
- data/lib/action_dispatch/http/headers.rb +1 -1
- data/lib/action_dispatch/http/mime_negotiation.rb +2 -2
- data/lib/action_dispatch/http/parameters.rb +8 -1
- data/lib/action_dispatch/http/request.rb +0 -12
- data/lib/action_dispatch/http/response.rb +15 -8
- data/lib/action_dispatch/journey/formatter.rb +3 -1
- data/lib/action_dispatch/journey/parser.rb +2 -0
- data/lib/action_dispatch/journey/parser_extras.rb +4 -2
- data/lib/action_dispatch/journey/route.rb +5 -3
- data/lib/action_dispatch/journey/router.rb +3 -1
- data/lib/action_dispatch/journey/visitors.rb +3 -1
- data/lib/action_dispatch/middleware/cookies.rb +3 -3
- data/lib/action_dispatch/middleware/debug_exceptions.rb +5 -1
- data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +2 -2
- data/lib/action_dispatch/middleware/ssl.rb +14 -5
- data/lib/action_dispatch/middleware/static.rb +3 -3
- data/lib/action_dispatch/routing/mapper.rb +74 -61
- data/lib/action_dispatch/routing/redirection.rb +0 -1
- data/lib/action_dispatch/testing/integration.rb +150 -196
- data/lib/action_dispatch/testing/request_encoder.rb +53 -0
- data/lib/action_dispatch/testing/test_process.rb +2 -1
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +7 -2
- data/lib/action_pack/gem_version.rb +2 -2
- metadata +13 -12
data/lib/action_dispatch.rb
CHANGED
@@ -3,7 +3,7 @@ module ActionDispatch
|
|
3
3
|
# Provides access to the request's HTTP headers from the environment.
|
4
4
|
#
|
5
5
|
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
6
|
-
# headers = ActionDispatch::Http::Headers.
|
6
|
+
# headers = ActionDispatch::Http::Headers.from_hash(env)
|
7
7
|
# headers["Content-Type"] # => "text/plain"
|
8
8
|
# headers["User-Agent"] # => "curl/7.43.0"
|
9
9
|
#
|
@@ -29,8 +29,8 @@ module ActionDispatch
|
|
29
29
|
content_mime_type && content_mime_type.to_s
|
30
30
|
end
|
31
31
|
|
32
|
-
def has_content_type?
|
33
|
-
|
32
|
+
def has_content_type? # :nodoc:
|
33
|
+
get_header "CONTENT_TYPE"
|
34
34
|
end
|
35
35
|
|
36
36
|
# Returns the accepted MIME type for the request.
|
@@ -44,7 +44,14 @@ module ActionDispatch
|
|
44
44
|
|
45
45
|
def path_parameters=(parameters) #:nodoc:
|
46
46
|
delete_header('action_dispatch.request.parameters')
|
47
|
+
|
48
|
+
# If any of the path parameters has an invalid encoding then
|
49
|
+
# raise since it's likely to trigger errors further on.
|
50
|
+
Request::Utils.check_param_encoding(parameters)
|
51
|
+
|
47
52
|
set_header PARAMETERS_KEY, parameters
|
53
|
+
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
|
54
|
+
raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}")
|
48
55
|
end
|
49
56
|
|
50
57
|
# Returns a hash with the \parameters used to form the \path of the request.
|
@@ -58,7 +65,7 @@ module ActionDispatch
|
|
58
65
|
private
|
59
66
|
|
60
67
|
def parse_formatted_parameters(parsers)
|
61
|
-
return yield if content_length.zero?
|
68
|
+
return yield if content_length.zero? || content_mime_type.nil?
|
62
69
|
|
63
70
|
strategy = parsers.fetch(content_mime_type.symbol) { return yield }
|
64
71
|
|
@@ -66,24 +66,12 @@ module ActionDispatch
|
|
66
66
|
def commit_cookie_jar! # :nodoc:
|
67
67
|
end
|
68
68
|
|
69
|
-
def check_path_parameters!
|
70
|
-
# If any of the path parameters has an invalid encoding then
|
71
|
-
# raise since it's likely to trigger errors further on.
|
72
|
-
path_parameters.each do |key, value|
|
73
|
-
next unless value.respond_to?(:valid_encoding?)
|
74
|
-
unless value.valid_encoding?
|
75
|
-
raise ActionController::BadRequest, "Invalid parameter encoding: #{key} => #{value.inspect}"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
69
|
PASS_NOT_FOUND = Class.new { # :nodoc:
|
81
70
|
def self.action(_); self; end
|
82
71
|
def self.call(_); [404, {'X-Cascade' => 'pass'}, []]; end
|
83
72
|
}
|
84
73
|
|
85
74
|
def controller_class
|
86
|
-
check_path_parameters!
|
87
75
|
params = path_parameters
|
88
76
|
|
89
77
|
if params.key?(:controller)
|
@@ -224,8 +224,10 @@ module ActionDispatch # :nodoc:
|
|
224
224
|
|
225
225
|
# Sets the HTTP content type.
|
226
226
|
def content_type=(content_type)
|
227
|
-
|
228
|
-
|
227
|
+
return unless content_type
|
228
|
+
new_header_info = parse_content_type(content_type.to_s)
|
229
|
+
prev_header_info = parsed_content_type_header
|
230
|
+
set_content_type new_header_info.mime_type, new_header_info.charset || prev_header_info.charset || self.class.default_charset
|
229
231
|
end
|
230
232
|
|
231
233
|
# Sets the HTTP response's content MIME type. For example, in the controller
|
@@ -238,7 +240,7 @@ module ActionDispatch # :nodoc:
|
|
238
240
|
# information.
|
239
241
|
|
240
242
|
def content_type
|
241
|
-
|
243
|
+
parsed_content_type_header.mime_type
|
242
244
|
end
|
243
245
|
|
244
246
|
def sending_file=(v)
|
@@ -253,7 +255,7 @@ module ActionDispatch # :nodoc:
|
|
253
255
|
# response.charset = 'utf-16' # => 'utf-16'
|
254
256
|
# response.charset = nil # => 'utf-8'
|
255
257
|
def charset=(charset)
|
256
|
-
header_info =
|
258
|
+
header_info = parsed_content_type_header
|
257
259
|
if false == charset
|
258
260
|
set_header CONTENT_TYPE, header_info.mime_type
|
259
261
|
else
|
@@ -265,7 +267,7 @@ module ActionDispatch # :nodoc:
|
|
265
267
|
# The charset of the response. HTML wants to know the encoding of the
|
266
268
|
# content you're giving them, so we need to send that along.
|
267
269
|
def charset
|
268
|
-
header_info =
|
270
|
+
header_info = parsed_content_type_header
|
269
271
|
header_info.charset || self.class.default_charset
|
270
272
|
end
|
271
273
|
|
@@ -403,8 +405,7 @@ module ActionDispatch # :nodoc:
|
|
403
405
|
ContentTypeHeader = Struct.new :mime_type, :charset
|
404
406
|
NullContentTypeHeader = ContentTypeHeader.new nil, nil
|
405
407
|
|
406
|
-
def parse_content_type
|
407
|
-
content_type = get_header CONTENT_TYPE
|
408
|
+
def parse_content_type(content_type)
|
408
409
|
if content_type
|
409
410
|
type, charset = content_type.split(/;\s*charset=/)
|
410
411
|
type = nil if type.empty?
|
@@ -414,6 +415,12 @@ module ActionDispatch # :nodoc:
|
|
414
415
|
end
|
415
416
|
end
|
416
417
|
|
418
|
+
# Small internal convenience method to get the parsed version of the current
|
419
|
+
# content type header.
|
420
|
+
def parsed_content_type_header
|
421
|
+
parse_content_type(get_header(CONTENT_TYPE))
|
422
|
+
end
|
423
|
+
|
417
424
|
def set_content_type(content_type, charset)
|
418
425
|
type = (content_type || '').dup
|
419
426
|
type << "; charset=#{charset}" if charset
|
@@ -450,7 +457,7 @@ module ActionDispatch # :nodoc:
|
|
450
457
|
def assign_default_content_type_and_charset!
|
451
458
|
return if content_type
|
452
459
|
|
453
|
-
ct =
|
460
|
+
ct = parsed_content_type_header
|
454
461
|
set_content_type(ct.mime_type || Mime[:html].to_s,
|
455
462
|
ct.charset || self.class.default_charset)
|
456
463
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'action_controller/metal/exceptions'
|
2
2
|
|
3
3
|
module ActionDispatch
|
4
|
+
# :stopdoc:
|
4
5
|
module Journey
|
5
6
|
# The Formatter class is used for formatting URLs. For example, parameters
|
6
7
|
# passed to +url_for+ in Rails will eventually call Formatter#generate.
|
7
|
-
class Formatter
|
8
|
+
class Formatter
|
8
9
|
attr_reader :routes
|
9
10
|
|
10
11
|
def initialize(routes)
|
@@ -174,4 +175,5 @@ module ActionDispatch
|
|
174
175
|
end
|
175
176
|
end
|
176
177
|
end
|
178
|
+
# :stopdoc:
|
177
179
|
end
|
@@ -9,6 +9,7 @@ require 'racc/parser.rb'
|
|
9
9
|
|
10
10
|
require 'action_dispatch/journey/parser_extras'
|
11
11
|
module ActionDispatch
|
12
|
+
# :stopdoc:
|
12
13
|
module Journey
|
13
14
|
class Parser < Racc::Parser
|
14
15
|
##### State transition tables begin ###
|
@@ -195,4 +196,5 @@ end
|
|
195
196
|
|
196
197
|
end # class Parser
|
197
198
|
end # module Journey
|
199
|
+
# :startdoc:
|
198
200
|
end # module ActionDispatch
|
@@ -2,8 +2,9 @@ require 'action_dispatch/journey/scanner'
|
|
2
2
|
require 'action_dispatch/journey/nodes/node'
|
3
3
|
|
4
4
|
module ActionDispatch
|
5
|
-
|
6
|
-
|
5
|
+
# :stopdoc:
|
6
|
+
module Journey
|
7
|
+
class Parser < Racc::Parser
|
7
8
|
include Journey::Nodes
|
8
9
|
|
9
10
|
def self.parse(string)
|
@@ -24,4 +25,5 @@ module ActionDispatch
|
|
24
25
|
end
|
25
26
|
end
|
26
27
|
end
|
28
|
+
# :startdoc:
|
27
29
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module ActionDispatch
|
2
|
-
|
3
|
-
|
2
|
+
# :stopdoc:
|
3
|
+
module Journey
|
4
|
+
class Route
|
4
5
|
attr_reader :app, :path, :defaults, :name, :precedence
|
5
6
|
|
6
7
|
attr_reader :constraints, :internal
|
@@ -81,7 +82,7 @@ module ActionDispatch
|
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
84
|
-
def requirements
|
85
|
+
def requirements
|
85
86
|
# needed for rails `rails routes`
|
86
87
|
@defaults.merge(path.requirements).delete_if { |_,v|
|
87
88
|
/.+?/ == v
|
@@ -177,4 +178,5 @@ module ActionDispatch
|
|
177
178
|
end
|
178
179
|
end
|
179
180
|
end
|
181
|
+
# :startdoc:
|
180
182
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module ActionDispatch
|
2
|
-
|
2
|
+
# :stopdoc:
|
3
|
+
module Journey
|
3
4
|
class Format
|
4
5
|
ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
|
5
6
|
ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
|
@@ -261,4 +262,5 @@ module ActionDispatch
|
|
261
262
|
end
|
262
263
|
end
|
263
264
|
end
|
265
|
+
# :startdoc:
|
264
266
|
end
|
@@ -372,7 +372,7 @@ module ActionDispatch
|
|
372
372
|
|
373
373
|
handle_options(options)
|
374
374
|
|
375
|
-
if @cookies[name.to_s] != value
|
375
|
+
if @cookies[name.to_s] != value || options[:expires]
|
376
376
|
@cookies[name.to_s] = value
|
377
377
|
@set_cookies[name.to_s] = options
|
378
378
|
@delete_cookies.delete(name.to_s)
|
@@ -576,8 +576,8 @@ module ActionDispatch
|
|
576
576
|
"Read the upgrade documentation to learn more about this new config option."
|
577
577
|
end
|
578
578
|
|
579
|
-
secret = key_generator.generate_key(request.encrypted_cookie_salt ||
|
580
|
-
sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt ||
|
579
|
+
secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
|
580
|
+
sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
|
581
581
|
@encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
|
582
582
|
end
|
583
583
|
|
@@ -165,7 +165,11 @@ module ActionDispatch
|
|
165
165
|
end
|
166
166
|
|
167
167
|
def log_array(logger, array)
|
168
|
-
|
168
|
+
if logger.formatter && logger.formatter.respond_to?(:tags_text)
|
169
|
+
logger.fatal array.join("\n#{logger.formatter.tags_text}")
|
170
|
+
else
|
171
|
+
logger.fatal array.join("\n")
|
172
|
+
end
|
169
173
|
end
|
170
174
|
|
171
175
|
def logger(request)
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module ActionDispatch
|
2
|
+
# This middleware can be used to diagnose deadlocks in the autoload interlock.
|
3
|
+
#
|
4
|
+
# To use it, insert it near the top of the middleware stack, using
|
5
|
+
# <tt>config/application.rb</tt>:
|
6
|
+
#
|
7
|
+
# config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks
|
8
|
+
#
|
9
|
+
# After restarting the application and re-triggering the deadlock condition,
|
10
|
+
# <tt>/rails/locks</tt> will show a summary of all threads currently known to
|
11
|
+
# the interlock, which lock level they are holding or awaiting, and their
|
12
|
+
# current backtrace.
|
13
|
+
#
|
14
|
+
# Generally a deadlock will be caused by the interlock conflicting with some
|
15
|
+
# other external lock or blocking I/O call. These cannot be automatically
|
16
|
+
# identified, but should be visible in the displayed backtraces.
|
17
|
+
#
|
18
|
+
# NOTE: The formatting and content of this middleware's output is intended for
|
19
|
+
# human consumption, and should be expected to change between releases.
|
20
|
+
#
|
21
|
+
# This middleware exposes operational details of the server, with no access
|
22
|
+
# control. It should only be enabled when in use, and removed thereafter.
|
23
|
+
class DebugLocks
|
24
|
+
def initialize(app, path = '/rails/locks')
|
25
|
+
@app = app
|
26
|
+
@path = path
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
req = ActionDispatch::Request.new env
|
31
|
+
|
32
|
+
if req.get?
|
33
|
+
path = req.path_info.chomp('/'.freeze)
|
34
|
+
if path == @path
|
35
|
+
return render_details(req)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@app.call(env)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def render_details(req)
|
44
|
+
threads = ActiveSupport::Dependencies.interlock.raw_state do |threads|
|
45
|
+
# The Interlock itself comes to a complete halt as long as this block
|
46
|
+
# is executing. That gives us a more consistent picture of everything,
|
47
|
+
# but creates a pretty strong Observer Effect.
|
48
|
+
#
|
49
|
+
# Most directly, that means we need to do as little as possible in
|
50
|
+
# this block. More widely, it means this middleware should remain a
|
51
|
+
# strictly diagnostic tool (to be used when something has gone wrong),
|
52
|
+
# and not for any sort of general monitoring.
|
53
|
+
|
54
|
+
threads.each.with_index do |(thread, info), idx|
|
55
|
+
info[:index] = idx
|
56
|
+
info[:backtrace] = thread.backtrace
|
57
|
+
end
|
58
|
+
|
59
|
+
threads
|
60
|
+
end
|
61
|
+
|
62
|
+
str = threads.map do |thread, info|
|
63
|
+
if info[:exclusive]
|
64
|
+
lock_state = 'Exclusive'
|
65
|
+
elsif info[:sharing] > 0
|
66
|
+
lock_state = 'Sharing'
|
67
|
+
lock_state << " x#{info[:sharing]}" if info[:sharing] > 1
|
68
|
+
else
|
69
|
+
lock_state = 'No lock'
|
70
|
+
end
|
71
|
+
|
72
|
+
if info[:waiting]
|
73
|
+
lock_state << ' (yielded share)'
|
74
|
+
end
|
75
|
+
|
76
|
+
msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n"
|
77
|
+
|
78
|
+
if info[:sleeper]
|
79
|
+
msg << " Waiting in #{info[:sleeper]}"
|
80
|
+
msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil?
|
81
|
+
msg << "\n"
|
82
|
+
|
83
|
+
if info[:compatible]
|
84
|
+
compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect }
|
85
|
+
msg << " may be pre-empted for: #{compat.join(', ')}\n"
|
86
|
+
end
|
87
|
+
|
88
|
+
blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) }
|
89
|
+
msg << " blocked by: #{blockers.map {|i| i[:index] }.join(', ')}\n" if blockers.any?
|
90
|
+
end
|
91
|
+
|
92
|
+
blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) }
|
93
|
+
msg << " blocking: #{blockees.map {|i| i[:index] }.join(', ')}\n" if blockees.any?
|
94
|
+
|
95
|
+
msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace]
|
96
|
+
end.join("\n\n---\n\n\n")
|
97
|
+
|
98
|
+
[200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]]
|
99
|
+
end
|
100
|
+
|
101
|
+
def blocked_by?(victim, blocker, all_threads)
|
102
|
+
return false if victim.equal?(blocker)
|
103
|
+
|
104
|
+
case victim[:sleeper]
|
105
|
+
when :start_sharing
|
106
|
+
blocker[:exclusive] ||
|
107
|
+
(!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false))
|
108
|
+
when :start_exclusive
|
109
|
+
blocker[:sharing] > 0 ||
|
110
|
+
blocker[:exclusive] ||
|
111
|
+
(blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose]))
|
112
|
+
when :yield_shares
|
113
|
+
blocker[:exclusive]
|
114
|
+
when :stop_exclusive
|
115
|
+
blocker[:exclusive] ||
|
116
|
+
victim[:compatible] &&
|
117
|
+
victim[:compatible].include?(blocker[:purpose]) &&
|
118
|
+
all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -17,8 +17,8 @@ module ActionDispatch
|
|
17
17
|
'ActionDispatch::ParamsParser::ParseError' => :bad_request,
|
18
18
|
'ActionController::BadRequest' => :bad_request,
|
19
19
|
'ActionController::ParameterMissing' => :bad_request,
|
20
|
-
'Rack::
|
21
|
-
'Rack::
|
20
|
+
'Rack::QueryParser::ParameterTypeError' => :bad_request,
|
21
|
+
'Rack::QueryParser::InvalidParameterError' => :bad_request
|
22
22
|
)
|
23
23
|
|
24
24
|
cattr_accessor :rescue_templates
|
@@ -18,17 +18,18 @@ module ActionDispatch
|
|
18
18
|
# Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
|
19
19
|
#
|
20
20
|
# Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
|
21
|
-
# * `expires`: How long, in seconds, these settings will stick.
|
22
|
-
#
|
23
|
-
#
|
21
|
+
# * `expires`: How long, in seconds, these settings will stick. The minimum
|
22
|
+
# required to qualify for browser preload lists is `18.weeks`. Defaults to
|
23
|
+
# `180.days` (recommended).
|
24
24
|
# * `subdomains`: Set to `true` to tell the browser to apply these settings
|
25
25
|
# to all subdomains. This protects your cookies from interception by a
|
26
|
-
# vulnerable site on a subdomain. Defaults to `
|
26
|
+
# vulnerable site on a subdomain. Defaults to `false`.
|
27
27
|
# * `preload`: Advertise that this site may be included in browsers'
|
28
28
|
# preloaded HSTS lists. HSTS protects your site on every visit *except the
|
29
29
|
# first visit* since it hasn't seen your HSTS header yet. To close this
|
30
30
|
# gap, browser vendors include a baked-in list of HSTS-enabled sites.
|
31
31
|
# Go to https://hstspreload.appspot.com to submit your site for inclusion.
|
32
|
+
# Defaults to `false`.
|
32
33
|
#
|
33
34
|
# To turn off HSTS, omitting the header is not enough. Browsers will remember the
|
34
35
|
# original HSTS directive until it expires. Instead, use the header to tell browsers to
|
@@ -132,12 +133,20 @@ module ActionDispatch
|
|
132
133
|
end
|
133
134
|
|
134
135
|
def redirect_to_https(request)
|
135
|
-
[ @redirect.fetch(:status,
|
136
|
+
[ @redirect.fetch(:status, redirection_status(request)),
|
136
137
|
{ 'Content-Type' => 'text/html',
|
137
138
|
'Location' => https_location_for(request) },
|
138
139
|
@redirect.fetch(:body, []) ]
|
139
140
|
end
|
140
141
|
|
142
|
+
def redirection_status(request)
|
143
|
+
if request.get? || request.head?
|
144
|
+
301 # Issue a permanent redirect via a GET request.
|
145
|
+
else
|
146
|
+
307 # Issue a fresh request redirect to preserve the HTTP method.
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
141
150
|
def https_location_for(request)
|
142
151
|
host = @redirect[:host] || request.host
|
143
152
|
port = @redirect[:port] || request.port
|