actionpack 5.2.1 → 7.0.2.4

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.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -78,10 +78,10 @@ module ActionDispatch # :nodoc:
78
78
  x
79
79
  end
80
80
 
81
- CONTENT_TYPE = "Content-Type".freeze
82
- SET_COOKIE = "Set-Cookie".freeze
83
- LOCATION = "Location".freeze
84
- NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
81
+ CONTENT_TYPE = "Content-Type"
82
+ SET_COOKIE = "Set-Cookie"
83
+ LOCATION = "Location"
84
+ NO_CONTENT_CODES = [100, 101, 102, 103, 204, 205, 304]
85
85
 
86
86
  cattr_accessor :default_charset, default: "utf-8"
87
87
  cattr_accessor :default_headers
@@ -105,7 +105,7 @@ module ActionDispatch # :nodoc:
105
105
 
106
106
  def body
107
107
  @str_body ||= begin
108
- buf = "".dup
108
+ buf = +""
109
109
  each { |chunk| buf << chunk }
110
110
  buf
111
111
  end
@@ -142,7 +142,6 @@ module ActionDispatch # :nodoc:
142
142
  end
143
143
 
144
144
  private
145
-
146
145
  def each_chunk(&block)
147
146
  @buf.each(&block)
148
147
  end
@@ -224,16 +223,6 @@ module ActionDispatch # :nodoc:
224
223
  @status = Rack::Utils.status_code(status)
225
224
  end
226
225
 
227
- # Sets the HTTP content type.
228
- def content_type=(content_type)
229
- return unless content_type
230
- new_header_info = parse_content_type(content_type.to_s)
231
- prev_header_info = parsed_content_type_header
232
- charset = new_header_info.charset || prev_header_info.charset
233
- charset ||= self.class.default_charset unless prev_header_info.mime_type
234
- set_content_type new_header_info.mime_type, charset
235
- end
236
-
237
226
  # Sets the HTTP response's content MIME type. For example, in the controller
238
227
  # you could write this:
239
228
  #
@@ -242,8 +231,22 @@ module ActionDispatch # :nodoc:
242
231
  # If a character set has been defined for this response (see charset=) then
243
232
  # the character set information will also be included in the content type
244
233
  # information.
234
+ def content_type=(content_type)
235
+ return unless content_type
236
+ new_header_info = parse_content_type(content_type.to_s)
237
+ prev_header_info = parsed_content_type_header
238
+ charset = new_header_info.charset || prev_header_info.charset
239
+ charset ||= self.class.default_charset unless prev_header_info.mime_type
240
+ set_content_type new_header_info.mime_type, charset
241
+ end
245
242
 
243
+ # Content type of response.
246
244
  def content_type
245
+ super.presence
246
+ end
247
+
248
+ # Media type of response.
249
+ def media_type
247
250
  parsed_content_type_header.mime_type
248
251
  end
249
252
 
@@ -321,7 +324,7 @@ module ActionDispatch # :nodoc:
321
324
  # Avoid having to pass an open file handle as the response body.
322
325
  # Rack::Sendfile will usually intercept the response and uses
323
326
  # the path directly, so there is no reason to open the file.
324
- class FileBody #:nodoc:
327
+ class FileBody # :nodoc:
325
328
  attr_reader :to_path
326
329
 
327
330
  def initialize(path)
@@ -404,15 +407,18 @@ module ActionDispatch # :nodoc:
404
407
  end
405
408
 
406
409
  private
407
-
408
410
  ContentTypeHeader = Struct.new :mime_type, :charset
409
411
  NullContentTypeHeader = ContentTypeHeader.new nil, nil
410
412
 
413
+ CONTENT_TYPE_PARSER = /
414
+ \A
415
+ (?<mime_type>[^;\s]+\s*(?:;\s*(?:(?!charset)[^;\s])+)*)?
416
+ (?:;\s*charset=(?<quote>"?)(?<charset>[^;\s]+)\k<quote>)?
417
+ /x # :nodoc:
418
+
411
419
  def parse_content_type(content_type)
412
- if content_type
413
- type, charset = content_type.split(/;\s*charset=/)
414
- type = nil if type && type.empty?
415
- ContentTypeHeader.new(type, charset)
420
+ if content_type && match = CONTENT_TYPE_PARSER.match(content_type)
421
+ ContentTypeHeader.new(match[:mime_type], match[:charset])
416
422
  else
417
423
  NullContentTypeHeader
418
424
  end
@@ -425,8 +431,8 @@ module ActionDispatch # :nodoc:
425
431
  end
426
432
 
427
433
  def set_content_type(content_type, charset)
428
- type = (content_type || "").dup
429
- type << "; charset=#{charset.to_s.downcase}" if charset
434
+ type = content_type || ""
435
+ type = "#{type}; charset=#{charset.to_s.downcase}" if charset
430
436
  set_header CONTENT_TYPE, type
431
437
  end
432
438
 
@@ -459,7 +465,7 @@ module ActionDispatch # :nodoc:
459
465
  end
460
466
 
461
467
  def assign_default_content_type_and_charset!
462
- return if content_type
468
+ return if media_type
463
469
 
464
470
  ct = parsed_content_type_header
465
471
  set_content_type(ct.mime_type || Mime[:html].to_s,
@@ -486,7 +492,7 @@ module ActionDispatch # :nodoc:
486
492
  end
487
493
 
488
494
  def respond_to?(method, include_private = false)
489
- if method.to_s == "to_path"
495
+ if method.to_sym == :to_path
490
496
  @response.stream.respond_to?(method)
491
497
  else
492
498
  super
@@ -517,4 +523,6 @@ module ActionDispatch # :nodoc:
517
523
  end
518
524
  end
519
525
  end
526
+
527
+ ActiveSupport.run_load_hooks(:action_dispatch_response, Response)
520
528
  end
@@ -20,7 +20,6 @@ module ActionDispatch
20
20
  # A +Tempfile+ object with the actual uploaded file. Note that some of
21
21
  # its interface is available directly.
22
22
  attr_accessor :tempfile
23
- alias :to_io :tempfile
24
23
 
25
24
  # A string with the headers of the multipart request.
26
25
  attr_accessor :headers
@@ -65,6 +64,11 @@ module ActionDispatch
65
64
  @tempfile.path
66
65
  end
67
66
 
67
+ # Shortcut for +tempfile.to_path+.
68
+ def to_path
69
+ @tempfile.to_path
70
+ end
71
+
68
72
  # Shortcut for +tempfile.rewind+.
69
73
  def rewind
70
74
  @tempfile.rewind
@@ -79,6 +83,10 @@ module ActionDispatch
79
83
  def eof?
80
84
  @tempfile.eof?
81
85
  end
86
+
87
+ def to_io
88
+ @tempfile.to_io
89
+ end
82
90
  end
83
91
  end
84
92
  end
@@ -9,6 +9,7 @@ module ActionDispatch
9
9
  HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
10
10
  PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
11
11
 
12
+ mattr_accessor :secure_protocol, default: false
12
13
  mattr_accessor :tld_length, default: 1
13
14
 
14
15
  class << self
@@ -67,10 +68,11 @@ module ActionDispatch
67
68
  end
68
69
 
69
70
  def path_for(options)
70
- path = options[:script_name].to_s.chomp("/".freeze)
71
+ path = options[:script_name].to_s.chomp("/")
71
72
  path << options[:path] if options.key?(:path)
72
73
 
73
- add_trailing_slash(path) if options[:trailing_slash]
74
+ path = "/" if options[:trailing_slash] && path.blank?
75
+
74
76
  add_params(path, options[:params]) if options.key?(:params)
75
77
  add_anchor(path, options[:anchor]) if options.key?(:anchor)
76
78
 
@@ -78,109 +80,100 @@ module ActionDispatch
78
80
  end
79
81
 
80
82
  private
81
-
82
- def add_params(path, params)
83
- params = { params: params } unless params.is_a?(Hash)
84
- params.reject! { |_, v| v.to_param.nil? }
85
- query = params.to_query
86
- path << "?#{query}" unless query.empty?
87
- end
88
-
89
- def add_anchor(path, anchor)
90
- if anchor
91
- path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
83
+ def add_params(path, params)
84
+ params = { params: params } unless params.is_a?(Hash)
85
+ params.reject! { |_, v| v.to_param.nil? }
86
+ query = params.to_query
87
+ path << "?#{query}" unless query.empty?
92
88
  end
93
- end
94
-
95
- def extract_domain_from(host, tld_length)
96
- host.split(".").last(1 + tld_length).join(".")
97
- end
98
89
 
99
- def extract_subdomains_from(host, tld_length)
100
- parts = host.split(".")
101
- parts[0..-(tld_length + 2)]
102
- end
90
+ def add_anchor(path, anchor)
91
+ if anchor
92
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
93
+ end
94
+ end
103
95
 
104
- def add_trailing_slash(path)
105
- if path.include?("?")
106
- path.sub!(/\?/, '/\&')
107
- elsif !path.include?(".")
108
- path.sub!(/[^\/]\z|\A\z/, '\&/')
96
+ def extract_domain_from(host, tld_length)
97
+ host.split(".").last(1 + tld_length).join(".")
109
98
  end
110
- end
111
99
 
112
- def build_host_url(host, port, protocol, options, path)
113
- if match = host.match(HOST_REGEXP)
114
- protocol ||= match[1] unless protocol == false
115
- host = match[2]
116
- port = match[3] unless options.key? :port
100
+ def extract_subdomains_from(host, tld_length)
101
+ parts = host.split(".")
102
+ parts[0..-(tld_length + 2)]
117
103
  end
118
104
 
119
- protocol = normalize_protocol protocol
120
- host = normalize_host(host, options)
105
+ def build_host_url(host, port, protocol, options, path)
106
+ if match = host.match(HOST_REGEXP)
107
+ protocol ||= match[1] unless protocol == false
108
+ host = match[2]
109
+ port = match[3] unless options.key? :port
110
+ end
121
111
 
122
- result = protocol.dup
112
+ protocol = normalize_protocol protocol
113
+ host = normalize_host(host, options)
123
114
 
124
- if options[:user] && options[:password]
125
- result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
126
- end
115
+ result = protocol.dup
127
116
 
128
- result << host
129
- normalize_port(port, protocol) { |normalized_port|
130
- result << ":#{normalized_port}"
131
- }
117
+ if options[:user] && options[:password]
118
+ result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
119
+ end
132
120
 
133
- result.concat path
134
- end
121
+ result << host
122
+ normalize_port(port, protocol) { |normalized_port|
123
+ result << ":#{normalized_port}"
124
+ }
135
125
 
136
- def named_host?(host)
137
- IP_HOST_REGEXP !~ host
138
- end
126
+ result.concat path
127
+ end
139
128
 
140
- def normalize_protocol(protocol)
141
- case protocol
142
- when nil
143
- "http://"
144
- when false, "//"
145
- "//"
146
- when PROTOCOL_REGEXP
147
- "#{$1}://"
148
- else
149
- raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
129
+ def named_host?(host)
130
+ !IP_HOST_REGEXP.match?(host)
150
131
  end
151
- end
152
132
 
153
- def normalize_host(_host, options)
154
- return _host unless named_host?(_host)
133
+ def normalize_protocol(protocol)
134
+ case protocol
135
+ when nil
136
+ secure_protocol ? "https://" : "http://"
137
+ when false, "//"
138
+ "//"
139
+ when PROTOCOL_REGEXP
140
+ "#{$1}://"
141
+ else
142
+ raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
143
+ end
144
+ end
145
+
146
+ def normalize_host(_host, options)
147
+ return _host unless named_host?(_host)
155
148
 
156
- tld_length = options[:tld_length] || @@tld_length
157
- subdomain = options.fetch :subdomain, true
158
- domain = options[:domain]
149
+ tld_length = options[:tld_length] || @@tld_length
150
+ subdomain = options.fetch :subdomain, true
151
+ domain = options[:domain]
159
152
 
160
- host = "".dup
161
- if subdomain == true
162
- return _host if domain.nil?
153
+ host = +""
154
+ if subdomain == true
155
+ return _host if domain.nil?
163
156
 
164
- host << extract_subdomains_from(_host, tld_length).join(".")
165
- elsif subdomain
166
- host << subdomain.to_param
157
+ host << extract_subdomains_from(_host, tld_length).join(".")
158
+ elsif subdomain
159
+ host << subdomain.to_param
160
+ end
161
+ host << "." unless host.empty?
162
+ host << (domain || extract_domain_from(_host, tld_length))
163
+ host
167
164
  end
168
- host << "." unless host.empty?
169
- host << (domain || extract_domain_from(_host, tld_length))
170
- host
171
- end
172
165
 
173
- def normalize_port(port, protocol)
174
- return unless port
166
+ def normalize_port(port, protocol)
167
+ return unless port
175
168
 
176
- case protocol
177
- when "//" then yield port
178
- when "https://"
179
- yield port unless port.to_i == 443
180
- else
181
- yield port unless port.to_i == 80
169
+ case protocol
170
+ when "//" then yield port
171
+ when "https://"
172
+ yield port unless port.to_i == 443
173
+ else
174
+ yield port unless port.to_i == 80
175
+ end
182
176
  end
183
- end
184
177
  end
185
178
 
186
179
  def initialize
@@ -222,7 +215,7 @@ module ActionDispatch
222
215
  if forwarded = x_forwarded_host.presence
223
216
  forwarded.split(/,\s?/).last
224
217
  else
225
- get_header("HTTP_HOST") || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}"
218
+ get_header("HTTP_HOST") || "#{server_name}:#{get_header('SERVER_PORT')}"
226
219
  end
227
220
  end
228
221
 
@@ -231,7 +224,7 @@ module ActionDispatch
231
224
  # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
232
225
  # req.host # => "example.com"
233
226
  def host
234
- raw_host_with_port.sub(/:\d+$/, "".freeze)
227
+ raw_host_with_port.sub(/:\d+$/, "")
235
228
  end
236
229
 
237
230
  # Returns a \host:\port string for this request, such as "example.com" or
@@ -258,12 +251,10 @@ module ActionDispatch
258
251
  # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
259
252
  # req.port # => 8080
260
253
  def port
261
- @port ||= begin
262
- if raw_host_with_port =~ /:(\d+)$/
263
- $1.to_i
264
- else
265
- standard_port
266
- end
254
+ @port ||= if raw_host_with_port =~ /:(\d+)$/
255
+ $1.to_i
256
+ else
257
+ standard_port
267
258
  end
268
259
  end
269
260
 
@@ -272,9 +263,10 @@ module ActionDispatch
272
263
  # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080'
273
264
  # req.standard_port # => 80
274
265
  def standard_port
275
- case protocol
276
- when "https://" then 443
277
- else 80
266
+ if "https://" == protocol
267
+ 443
268
+ else
269
+ 80
278
270
  end
279
271
  end
280
272
 
@@ -15,12 +15,53 @@ module ActionDispatch
15
15
  @cache = nil
16
16
  end
17
17
 
18
- def generate(name, options, path_parameters, parameterize = nil)
18
+ class RouteWithParams
19
+ attr_reader :params
20
+
21
+ def initialize(route, parameterized_parts, params)
22
+ @route = route
23
+ @parameterized_parts = parameterized_parts
24
+ @params = params
25
+ end
26
+
27
+ def path(_)
28
+ @route.format(@parameterized_parts)
29
+ end
30
+ end
31
+
32
+ class MissingRoute
33
+ attr_reader :routes, :name, :constraints, :missing_keys, :unmatched_keys
34
+
35
+ def initialize(constraints, missing_keys, unmatched_keys, routes, name)
36
+ @constraints = constraints
37
+ @missing_keys = missing_keys
38
+ @unmatched_keys = unmatched_keys
39
+ @routes = routes
40
+ @name = name
41
+ end
42
+
43
+ def path(method_name)
44
+ raise ActionController::UrlGenerationError.new(message, routes, name, method_name)
45
+ end
46
+
47
+ def params
48
+ path("unknown")
49
+ end
50
+
51
+ def message
52
+ message = +"No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}"
53
+ message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
54
+ message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
55
+ message
56
+ end
57
+ end
58
+
59
+ def generate(name, options, path_parameters)
19
60
  constraints = path_parameters.merge(options)
20
61
  missing_keys = nil
21
62
 
22
63
  match_route(name, constraints) do |route|
23
- parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
64
+ parameterized_parts = extract_parameterized_parts(route, options, path_parameters)
24
65
 
25
66
  # Skip this route unless a name has been provided or it is a
26
67
  # standard Rails route since we can't determine whether an options
@@ -44,17 +85,13 @@ module ActionDispatch
44
85
  parameterized_parts.delete(key)
45
86
  end
46
87
 
47
- return [route.format(parameterized_parts), params]
88
+ return RouteWithParams.new(route, parameterized_parts, params)
48
89
  end
49
90
 
50
91
  unmatched_keys = (missing_keys || []) & constraints.keys
51
92
  missing_keys = (missing_keys || []) - unmatched_keys
52
93
 
53
- message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}".dup
54
- message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
55
- message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty?
56
-
57
- raise ActionController::UrlGenerationError, message
94
+ MissingRoute.new(constraints, missing_keys, unmatched_keys, routes, name)
58
95
  end
59
96
 
60
97
  def clear
@@ -62,25 +99,26 @@ module ActionDispatch
62
99
  end
63
100
 
64
101
  private
65
-
66
- def extract_parameterized_parts(route, options, recall, parameterize = nil)
102
+ def extract_parameterized_parts(route, options, recall)
67
103
  parameterized_parts = recall.merge(options)
68
104
 
69
105
  keys_to_keep = route.parts.reverse_each.drop_while { |part|
70
- !options.key?(part) || (options[part] || recall[part]).nil?
106
+ !(options.key?(part) || route.scope_options.key?(part)) || (options[part].nil? && recall[part].nil?)
71
107
  } | route.required_parts
72
108
 
73
109
  parameterized_parts.delete_if do |bad_key, _|
74
110
  !keys_to_keep.include?(bad_key)
75
111
  end
76
112
 
77
- if parameterize
78
- parameterized_parts.each do |k, v|
79
- parameterized_parts[k] = parameterize.call(k, v)
113
+ parameterized_parts.each do |k, v|
114
+ if k == :controller
115
+ parameterized_parts[k] = v
116
+ else
117
+ parameterized_parts[k] = v.to_param
80
118
  end
81
119
  end
82
120
 
83
- parameterized_parts.keep_if { |_, v| v }
121
+ parameterized_parts.compact!
84
122
  parameterized_parts
85
123
  end
86
124
 
@@ -126,19 +164,10 @@ module ActionDispatch
126
164
  routes
127
165
  end
128
166
 
129
- module RegexCaseComparator
130
- DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
131
- DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
132
-
133
- def self.===(regex)
134
- DEFAULT_INPUT == regex
135
- end
136
- end
137
-
138
167
  # Returns an array populated with missing keys if any are present.
139
168
  def missing_keys(route, parts)
140
169
  missing_keys = nil
141
- tests = route.path.requirements
170
+ tests = route.path.requirements_for_missing_keys_check
142
171
  route.required_parts.each { |key|
143
172
  case tests[key]
144
173
  when nil
@@ -146,13 +175,8 @@ module ActionDispatch
146
175
  missing_keys ||= []
147
176
  missing_keys << key
148
177
  end
149
- when RegexCaseComparator
150
- unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
151
- missing_keys ||= []
152
- missing_keys << key
153
- end
154
178
  else
155
- unless /\A#{tests[key]}\Z/ === parts[key]
179
+ unless tests[key].match?(parts[key])
156
180
  missing_keys ||= []
157
181
  missing_keys << key
158
182
  end