actionpack 4.1.16 → 4.2.0.beta1

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.

Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +163 -690
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +0 -3
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/rendering.rb +1 -7
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller.rb +1 -0
  11. data/lib/action_controller/base.rb +2 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/caching/fragments.rb +7 -1
  14. data/lib/action_controller/log_subscriber.rb +26 -25
  15. data/lib/action_controller/metal.rb +11 -7
  16. data/lib/action_controller/metal/conditional_get.rb +31 -6
  17. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  18. data/lib/action_controller/metal/force_ssl.rb +1 -1
  19. data/lib/action_controller/metal/head.rb +2 -0
  20. data/lib/action_controller/metal/http_authentication.rb +3 -15
  21. data/lib/action_controller/metal/instrumentation.rb +4 -7
  22. data/lib/action_controller/metal/live.rb +57 -6
  23. data/lib/action_controller/metal/mime_responds.rb +17 -227
  24. data/lib/action_controller/metal/redirecting.rb +14 -8
  25. data/lib/action_controller/metal/renderers.rb +19 -3
  26. data/lib/action_controller/metal/rendering.rb +2 -6
  27. data/lib/action_controller/metal/request_forgery_protection.rb +75 -7
  28. data/lib/action_controller/metal/streaming.rb +1 -1
  29. data/lib/action_controller/metal/strong_parameters.rb +111 -11
  30. data/lib/action_controller/metal/url_for.rb +11 -12
  31. data/lib/action_controller/model_naming.rb +1 -1
  32. data/lib/action_controller/railtie.rb +4 -0
  33. data/lib/action_controller/test_case.rb +87 -75
  34. data/lib/action_dispatch/http/cache.rb +1 -1
  35. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  36. data/lib/action_dispatch/http/headers.rb +43 -9
  37. data/lib/action_dispatch/http/mime_negotiation.rb +10 -4
  38. data/lib/action_dispatch/http/mime_type.rb +2 -16
  39. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  40. data/lib/action_dispatch/http/parameters.rb +11 -26
  41. data/lib/action_dispatch/http/request.rb +30 -10
  42. data/lib/action_dispatch/http/response.rb +52 -17
  43. data/lib/action_dispatch/http/upload.rb +3 -8
  44. data/lib/action_dispatch/http/url.rb +87 -70
  45. data/lib/action_dispatch/journey/formatter.rb +18 -17
  46. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  47. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  48. data/lib/action_dispatch/journey/gtg/transition_table.rb +18 -26
  49. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  50. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  51. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  52. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  53. data/lib/action_dispatch/journey/parser.rb +52 -60
  54. data/lib/action_dispatch/journey/parser.y +11 -10
  55. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  56. data/lib/action_dispatch/journey/route.rb +3 -18
  57. data/lib/action_dispatch/journey/router.rb +34 -65
  58. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  59. data/lib/action_dispatch/journey/routes.rb +0 -4
  60. data/lib/action_dispatch/journey/visitors.rb +81 -92
  61. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  62. data/lib/action_dispatch/middleware/cookies.rb +27 -31
  63. data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -3
  64. data/lib/action_dispatch/middleware/exception_wrapper.rb +19 -17
  65. data/lib/action_dispatch/middleware/flash.rb +7 -4
  66. data/lib/action_dispatch/middleware/public_exceptions.rb +13 -8
  67. data/lib/action_dispatch/middleware/remote_ip.rb +3 -3
  68. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  69. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  70. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  71. data/lib/action_dispatch/middleware/static.rb +22 -23
  72. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +22 -18
  73. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +36 -8
  74. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  75. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  76. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  78. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  79. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  80. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +119 -63
  81. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  82. data/lib/action_dispatch/routing/inspector.rb +4 -11
  83. data/lib/action_dispatch/routing/mapper.rb +399 -278
  84. data/lib/action_dispatch/routing/polymorphic_routes.rb +190 -78
  85. data/lib/action_dispatch/routing/redirection.rb +10 -12
  86. data/lib/action_dispatch/routing/route_set.rb +224 -177
  87. data/lib/action_dispatch/routing/url_for.rb +9 -4
  88. data/lib/action_dispatch/testing/assertions.rb +11 -7
  89. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  90. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  91. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  92. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  93. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  94. data/lib/action_dispatch/testing/integration.rb +15 -18
  95. data/lib/action_dispatch/testing/test_request.rb +1 -1
  96. data/lib/action_dispatch/testing/test_response.rb +5 -1
  97. data/lib/action_pack/gem_version.rb +3 -3
  98. metadata +57 -15
  99. data/lib/action_controller/metal/responder.rb +0 -297
@@ -54,8 +54,14 @@ module ActionDispatch
54
54
  end
55
55
 
56
56
  def formats
57
- @env["action_dispatch.request.formats"] ||=
58
- if parameters[:format]
57
+ @env["action_dispatch.request.formats"] ||= begin
58
+ params_readable = begin
59
+ parameters[:format]
60
+ rescue ActionController::BadRequest
61
+ false
62
+ end
63
+
64
+ if params_readable
59
65
  Array(Mime[parameters[:format]])
60
66
  elsif use_accept_header && valid_accept_header
61
67
  accepts
@@ -64,13 +70,13 @@ module ActionDispatch
64
70
  else
65
71
  [Mime::HTML]
66
72
  end
73
+ end
67
74
  end
68
-
69
75
  # Sets the \variant for template.
70
76
  def variant=(variant)
71
77
  if variant.is_a?(Symbol)
72
78
  @variant = [variant]
73
- elsif variant.nil? || variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
79
+ elsif variant.is_a?(Array) && variant.any? && variant.all?{ |v| v.is_a?(Symbol) }
74
80
  @variant = variant
75
81
  else
76
82
  raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols, not a #{variant.class}. " \
@@ -23,7 +23,7 @@ module Mime
23
23
 
24
24
  SET = Mimes.new
25
25
  EXTENSION_LOOKUP = {}
26
- LOOKUP = {}
26
+ LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
27
27
 
28
28
  class << self
29
29
  def [](type)
@@ -146,7 +146,7 @@ module Mime
146
146
  end
147
147
 
148
148
  def lookup(string)
149
- LOOKUP[string] || Type.new(string)
149
+ LOOKUP[string]
150
150
  end
151
151
 
152
152
  def lookup_by_extension(extension)
@@ -225,12 +225,9 @@ module Mime
225
225
  end
226
226
  end
227
227
 
228
- attr_reader :hash
229
-
230
228
  def initialize(string, symbol = nil, synonyms = [])
231
229
  @symbol, @synonyms = symbol, synonyms
232
230
  @string = string
233
- @hash = [@string, @synonyms, @symbol].hash
234
231
  end
235
232
 
236
233
  def to_s
@@ -264,13 +261,6 @@ module Mime
264
261
  end
265
262
  end
266
263
 
267
- def eql?(other)
268
- super || (self.class == other.class &&
269
- @string == other.string &&
270
- @synonyms == other.synonyms &&
271
- @symbol == other.symbol)
272
- end
273
-
274
264
  def =~(mime_type)
275
265
  return false if mime_type.blank?
276
266
  regexp = Regexp.new(Regexp.quote(mime_type.to_s))
@@ -284,10 +274,6 @@ module Mime
284
274
  end
285
275
 
286
276
 
287
- protected
288
-
289
- attr_reader :string, :synonyms
290
-
291
277
  private
292
278
 
293
279
  def to_ary; end
@@ -56,7 +56,7 @@ module ActionDispatch
56
56
  elsif value.is_a?(Array)
57
57
  value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
58
58
  elsif blocks.any?
59
- key = key.dup if key.duplicable?
59
+ key = key.dup
60
60
  value = value.dup if value.duplicable?
61
61
  blocks.each { |b| b.call(key, value) }
62
62
  end
@@ -1,13 +1,11 @@
1
1
  require 'active_support/core_ext/hash/keys'
2
2
  require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'active_support/deprecation'
3
4
 
4
5
  module ActionDispatch
5
6
  module Http
6
7
  module Parameters
7
- def initialize(env)
8
- super
9
- @symbolized_path_params = nil
10
- end
8
+ PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
11
9
 
12
10
  # Returns both GET and POST \parameters in a single hash.
13
11
  def parameters
@@ -18,55 +16,42 @@ module ActionDispatch
18
16
  query_parameters.dup
19
17
  end
20
18
  params.merge!(path_parameters)
21
- params.with_indifferent_access
22
19
  end
23
20
  end
24
21
  alias :params :parameters
25
22
 
26
23
  def path_parameters=(parameters) #:nodoc:
27
- @symbolized_path_params = nil
28
- @env.delete("action_dispatch.request.parameters")
29
- @env["action_dispatch.request.path_parameters"] = parameters
24
+ @env.delete('action_dispatch.request.parameters')
25
+ @env[PARAMETERS_KEY] = parameters
30
26
  end
31
27
 
32
- # The same as <tt>path_parameters</tt> with explicitly symbolized keys.
33
28
  def symbolized_path_parameters
34
- @symbolized_path_params ||= path_parameters.symbolize_keys
29
+ ActiveSupport::Deprecation.warn(
30
+ "`symbolized_path_parameters` is deprecated. Please use `path_parameters`"
31
+ )
32
+ path_parameters
35
33
  end
36
34
 
37
35
  # Returns a hash with the \parameters used to form the \path of the request.
38
36
  # Returned hash keys are strings:
39
37
  #
40
38
  # {'action' => 'my_action', 'controller' => 'my_controller'}
41
- #
42
- # See <tt>symbolized_path_parameters</tt> for symbolized keys.
43
39
  def path_parameters
44
- @env["action_dispatch.request.path_parameters"] ||= {}
45
- end
46
-
47
- def reset_parameters #:nodoc:
48
- @env.delete("action_dispatch.request.parameters")
40
+ @env[PARAMETERS_KEY] ||= {}
49
41
  end
50
42
 
51
43
  private
52
44
 
53
- # Convert nested Hash to HashWithIndifferentAccess
54
- # and UTF-8 encode both keys and values in nested Hash.
45
+ # Convert nested Hash to HashWithIndifferentAccess.
55
46
  #
56
- # TODO: Validate that the characters are UTF-8. If they aren't,
57
- # you'll get a weird error down the road, but our form handling
58
- # should really prevent that from happening
59
47
  def normalize_encode_params(params)
60
48
  case params
61
- when String
62
- params.force_encoding(Encoding::UTF_8).encode!
63
49
  when Hash
64
50
  if params.has_key?(:tempfile)
65
51
  UploadedFile.new(params)
66
52
  else
67
53
  params.each_with_object({}) do |(key, val), new_hash|
68
- new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key
69
- new_hash[new_key] = if val.is_a?(Array)
54
+ new_hash[key] = if val.is_a?(Array)
70
55
  val.map! { |el| normalize_encode_params(el) }
71
56
  else
72
57
  normalize_encode_params(val)
@@ -23,7 +23,7 @@ module ActionDispatch
23
23
  autoload :Session, 'action_dispatch/request/session'
24
24
  autoload :Utils, 'action_dispatch/request/utils'
25
25
 
26
- LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
26
+ LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
27
27
 
28
28
  ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
29
29
  PATH_TRANSLATED REMOTE_HOST
@@ -53,6 +53,17 @@ module ActionDispatch
53
53
  @uuid = nil
54
54
  end
55
55
 
56
+ def check_path_parameters!
57
+ # If any of the path parameters has an invalid encoding then
58
+ # raise since it's likely to trigger errors further on.
59
+ path_parameters.each do |key, value|
60
+ next unless value.respond_to?(:valid_encoding?)
61
+ unless value.valid_encoding?
62
+ raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
63
+ end
64
+ end
65
+ end
66
+
56
67
  def key?(key)
57
68
  @env.key?(key)
58
69
  end
@@ -64,6 +75,7 @@ module ActionDispatch
64
75
  # Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt)
65
76
  # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt)
66
77
  # Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt)
78
+ # Calendar Extensions to WebDAV (http://www.ietf.org/rfc/rfc4791.txt)
67
79
  # PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt)
68
80
  RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
69
81
  RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
@@ -71,9 +83,10 @@ module ActionDispatch
71
83
  RFC3648 = %w(ORDERPATCH)
72
84
  RFC3744 = %w(ACL)
73
85
  RFC5323 = %w(SEARCH)
86
+ RFC4791 = %w(MKCALENDAR)
74
87
  RFC5789 = %w(PATCH)
75
88
 
76
- HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
89
+ HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789
77
90
 
78
91
  HTTP_METHOD_LOOKUP = {}
79
92
 
@@ -152,6 +165,13 @@ module ActionDispatch
152
165
  Http::Headers.new(@env)
153
166
  end
154
167
 
168
+ # Returns a +String+ with the last requested path including their params.
169
+ #
170
+ # # get '/foo'
171
+ # request.original_fullpath # => '/foo'
172
+ #
173
+ # # get '/foo?bar'
174
+ # request.original_fullpath # => '/foo?bar'
155
175
  def original_fullpath
156
176
  @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
157
177
  end
@@ -189,8 +209,8 @@ module ActionDispatch
189
209
  end
190
210
 
191
211
  # Returns true if the "X-Requested-With" header contains "XMLHttpRequest"
192
- # (case-insensitive). All major JavaScript libraries send this header with
193
- # every Ajax request.
212
+ # (case-insensitive), which may need to be manually added depending on the
213
+ # choice of JavaScript libraries and frameworks.
194
214
  def xml_http_request?
195
215
  @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
196
216
  end
@@ -205,7 +225,7 @@ module ActionDispatch
205
225
  @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
206
226
  end
207
227
 
208
- # Returns the unique request id, which is based off either the X-Request-Id header that can
228
+ # Returns the unique request id, which is based on either the X-Request-Id header that can
209
229
  # be generated by a firewall, load balancer, or web server or by the RequestId middleware
210
230
  # (which sets the action_dispatch.request_id environment variable).
211
231
  #
@@ -271,16 +291,16 @@ module ActionDispatch
271
291
 
272
292
  # Override Rack's GET method to support indifferent access
273
293
  def GET
274
- @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
275
- rescue TypeError => e
294
+ @env["action_dispatch.request.query_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
295
+ rescue TypeError, Rack::Utils::InvalidParameterError => e
276
296
  raise ActionController::BadRequest.new(:query, e)
277
297
  end
278
298
  alias :query_parameters :GET
279
299
 
280
300
  # Override Rack's POST method to support indifferent access
281
301
  def POST
282
- @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge((normalize_encode_params(super) || {}))
283
- rescue TypeError => e
302
+ @env["action_dispatch.request.request_parameters"] ||= Utils.deep_munge(normalize_encode_params(super || {}))
303
+ rescue TypeError, Rack::Utils::InvalidParameterError => e
284
304
  raise ActionController::BadRequest.new(:request, e)
285
305
  end
286
306
  alias :request_parameters :POST
@@ -315,7 +335,7 @@ module ActionDispatch
315
335
 
316
336
  private
317
337
  def check_method(name)
318
- HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}")
338
+ HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
319
339
  name
320
340
  end
321
341
  end
@@ -97,6 +97,9 @@ module ActionDispatch # :nodoc:
97
97
  x
98
98
  end
99
99
 
100
+ def abort
101
+ end
102
+
100
103
  def close
101
104
  @response.commit!
102
105
  @closed = true
@@ -110,11 +113,10 @@ module ActionDispatch # :nodoc:
110
113
  # The underlying body, as a streamable object.
111
114
  attr_reader :stream
112
115
 
113
- def initialize(status = 200, header = {}, body = [], options = {})
116
+ def initialize(status = 200, header = {}, body = [])
114
117
  super()
115
118
 
116
- default_headers = options.fetch(:default_headers, self.class.default_headers)
117
- header = merge_default_headers(header, default_headers)
119
+ header = merge_default_headers(header, self.class.default_headers)
118
120
 
119
121
  self.body, self.header, self.status = body, header, status
120
122
 
@@ -208,18 +210,6 @@ module ActionDispatch # :nodoc:
208
210
  end
209
211
  alias_method :status_message, :message
210
212
 
211
- def respond_to?(method, include_private = false)
212
- if method.to_s == 'to_path'
213
- stream.respond_to?(method)
214
- else
215
- super
216
- end
217
- end
218
-
219
- def to_path
220
- stream.to_path
221
- end
222
-
223
213
  # Returns the content of the response as a string. This contains the contents
224
214
  # of any calls to <tt>render</tt>.
225
215
  def body
@@ -272,6 +262,17 @@ module ActionDispatch # :nodoc:
272
262
  stream.close if stream.respond_to?(:close)
273
263
  end
274
264
 
265
+ def abort
266
+ if stream.respond_to?(:abort)
267
+ stream.abort
268
+ elsif stream.respond_to?(:close)
269
+ # `stream.close` should really be reserved for a close from the
270
+ # other direction, but we must fall back to it for
271
+ # compatibility.
272
+ stream.close
273
+ end
274
+ end
275
+
275
276
  # Turns the Response into a Rack-compatible array of the status, headers,
276
277
  # and body.
277
278
  def to_a
@@ -309,7 +310,9 @@ module ActionDispatch # :nodoc:
309
310
  end
310
311
 
311
312
  def merge_default_headers(original, default)
312
- default.respond_to?(:merge) ? default.merge(original) : original
313
+ return original unless default.respond_to?(:merge)
314
+
315
+ default.merge(original)
313
316
  end
314
317
 
315
318
  def build_buffer(response, body)
@@ -336,6 +339,38 @@ module ActionDispatch # :nodoc:
336
339
  !@sending_file && @charset != false
337
340
  end
338
341
 
342
+ class RackBody
343
+ def initialize(response)
344
+ @response = response
345
+ end
346
+
347
+ def each(*args, &block)
348
+ @response.each(*args, &block)
349
+ end
350
+
351
+ def close
352
+ # Rack "close" maps to Response#abort, and *not* Response#close
353
+ # (which is used when the controller's finished writing)
354
+ @response.abort
355
+ end
356
+
357
+ def body
358
+ @response.body
359
+ end
360
+
361
+ def respond_to?(method, include_private = false)
362
+ if method.to_s == 'to_path'
363
+ @response.stream.respond_to?(method)
364
+ else
365
+ super
366
+ end
367
+ end
368
+
369
+ def to_path
370
+ @response.stream.to_path
371
+ end
372
+ end
373
+
339
374
  def rack_response(status, header)
340
375
  assign_default_content_type_and_charset!(header)
341
376
  handle_conditional_get!
@@ -346,7 +381,7 @@ module ActionDispatch # :nodoc:
346
381
  header.delete CONTENT_TYPE
347
382
  [status, header, []]
348
383
  else
349
- [status, header, Rack::BodyProxy.new(self){}]
384
+ [status, header, RackBody.new(self)]
350
385
  end
351
386
  end
352
387
  end
@@ -18,6 +18,7 @@ module ActionDispatch
18
18
  # A +Tempfile+ object with the actual uploaded file. Note that some of
19
19
  # its interface is available directly.
20
20
  attr_accessor :tempfile
21
+ alias :to_io :tempfile
21
22
 
22
23
  # A string with the headers of the multipart request.
23
24
  attr_accessor :headers
@@ -26,7 +27,8 @@ module ActionDispatch
26
27
  @tempfile = hash[:tempfile]
27
28
  raise(ArgumentError, ':tempfile is required') unless @tempfile
28
29
 
29
- @original_filename = encode_filename(hash[:filename])
30
+ @original_filename = hash[:filename]
31
+ @original_filename &&= @original_filename.encode "UTF-8"
30
32
  @content_type = hash[:type]
31
33
  @headers = hash[:head]
32
34
  end
@@ -65,13 +67,6 @@ module ActionDispatch
65
67
  def eof?
66
68
  @tempfile.eof?
67
69
  end
68
-
69
- private
70
-
71
- def encode_filename(filename)
72
- # Encode the filename in the utf8 encoding, unless it is nil
73
- filename.force_encoding(Encoding::UTF_8).encode! if filename
74
- end
75
70
  end
76
71
  end
77
72
  end
@@ -5,51 +5,81 @@ module ActionDispatch
5
5
  module Http
6
6
  module URL
7
7
  IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
8
- HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/
8
+ HOST_REGEXP = /(^[^:]+:\/\/)?([^:]+)(?::(\d+$))?/
9
9
  PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
10
10
 
11
11
  mattr_accessor :tld_length
12
12
  self.tld_length = 1
13
13
 
14
14
  class << self
15
- def extract_domain(host, tld_length = @@tld_length)
16
- host.split('.').last(1 + tld_length).join('.') if named_host?(host)
15
+ def extract_domain(host, tld_length)
16
+ extract_domain_from(host, tld_length) if named_host?(host)
17
17
  end
18
18
 
19
- def extract_subdomains(host, tld_length = @@tld_length)
19
+ def extract_subdomains(host, tld_length)
20
20
  if named_host?(host)
21
- parts = host.split('.')
22
- parts[0..-(tld_length + 2)]
21
+ extract_subdomains_from(host, tld_length)
23
22
  else
24
23
  []
25
24
  end
26
25
  end
27
26
 
28
- def extract_subdomain(host, tld_length = @@tld_length)
27
+ def extract_subdomain(host, tld_length)
29
28
  extract_subdomains(host, tld_length).join('.')
30
29
  end
31
30
 
32
- def url_for(options = {})
33
- options = options.dup
34
- path = options.delete(:script_name).to_s.chomp("/")
35
- path << options.delete(:path).to_s
31
+ def url_for(options)
32
+ if options[:only_path]
33
+ path_for options
34
+ else
35
+ full_url_for options
36
+ end
37
+ end
36
38
 
37
- add_trailing_slash(path) if options[:trailing_slash]
39
+ def full_url_for(options)
40
+ host = options[:host]
41
+ protocol = options[:protocol]
42
+ port = options[:port]
38
43
 
39
- params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
40
- params.reject! { |_,v| v.to_param.nil? }
44
+ unless host
45
+ raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
46
+ end
41
47
 
42
- result = build_host_url(options)
48
+ build_host_url(host, port, protocol, options, path_for(options))
49
+ end
43
50
 
44
- result << path
51
+ def path_for(options)
52
+ path = options[:script_name].to_s.chomp("/")
53
+ path << options[:path] if options.key?(:path)
45
54
 
46
- result << "?#{params.to_query}" unless params.empty?
47
- result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
48
- result
55
+ add_trailing_slash(path) if options[:trailing_slash]
56
+ add_params(path, options[:params]) if options.key?(:params)
57
+ add_anchor(path, options[:anchor]) if options.key?(:anchor)
58
+
59
+ path
49
60
  end
50
61
 
51
62
  private
52
63
 
64
+ def add_params(path, params)
65
+ params = { params: params } unless params.is_a?(Hash)
66
+ params.reject! { |_,v| v.to_param.nil? }
67
+ path << "?#{params.to_query}" unless params.empty?
68
+ end
69
+
70
+ def add_anchor(path, anchor)
71
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param.to_s)}"
72
+ end
73
+
74
+ def extract_domain_from(host, tld_length)
75
+ host.split('.').last(1 + tld_length).join('.')
76
+ end
77
+
78
+ def extract_subdomains_from(host, tld_length)
79
+ parts = host.split('.')
80
+ parts[0..-(tld_length + 2)]
81
+ end
82
+
53
83
  def add_trailing_slash(path)
54
84
  # includes querysting
55
85
  if path.include?('?')
@@ -58,54 +88,38 @@ module ActionDispatch
58
88
  elsif !path.include?(".")
59
89
  path.sub!(/[^\/]\z|\A\z/, '\&/')
60
90
  end
61
-
62
- path
63
91
  end
64
92
 
65
- def build_host_url(options)
66
- if options[:host].blank? && options[:only_path].blank?
67
- raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
93
+ def build_host_url(host, port, protocol, options, path)
94
+ if match = host.match(HOST_REGEXP)
95
+ protocol ||= match[1] unless protocol == false
96
+ host = match[2]
97
+ port = match[3] unless options.key? :port
68
98
  end
69
99
 
70
- result = ""
100
+ protocol = normalize_protocol protocol
101
+ host = normalize_host(host, options)
71
102
 
72
- unless options[:only_path]
73
- if match = options[:host].match(HOST_REGEXP)
74
- options[:protocol] ||= match[1] unless options[:protocol] == false
75
- options[:host] = match[2]
76
- options[:port] = match[3] unless options.key?(:port)
77
- end
103
+ result = protocol.dup
78
104
 
79
- options[:protocol] = normalize_protocol(options)
80
- options[:host] = normalize_host(options)
81
- options[:port] = normalize_port(options)
82
-
83
- result << options[:protocol]
84
- result << rewrite_authentication(options)
85
- result << options[:host]
86
- result << ":#{options[:port]}" if options[:port]
105
+ if options[:user] && options[:password]
106
+ result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
87
107
  end
88
- result
89
- end
90
108
 
91
- def named_host?(host)
92
- host && IP_HOST_REGEXP !~ host
93
- end
109
+ result << host
110
+ normalize_port(port, protocol) { |normalized_port|
111
+ result << ":#{normalized_port}"
112
+ }
94
113
 
95
- def same_host?(options)
96
- (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil?
114
+ result.concat path
97
115
  end
98
116
 
99
- def rewrite_authentication(options)
100
- if options[:user] && options[:password]
101
- "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
102
- else
103
- ""
104
- end
117
+ def named_host?(host)
118
+ IP_HOST_REGEXP !~ host
105
119
  end
106
120
 
107
- def normalize_protocol(options)
108
- case options[:protocol]
121
+ def normalize_protocol(protocol)
122
+ case protocol
109
123
  when nil
110
124
  "http://"
111
125
  when false, "//"
@@ -113,36 +127,39 @@ module ActionDispatch
113
127
  when PROTOCOL_REGEXP
114
128
  "#{$1}://"
115
129
  else
116
- raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}"
130
+ raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
117
131
  end
118
132
  end
119
133
 
120
- def normalize_host(options)
121
- return options[:host] if !named_host?(options[:host]) || same_host?(options)
134
+ def normalize_host(_host, options)
135
+ return _host unless named_host?(_host)
122
136
 
123
137
  tld_length = options[:tld_length] || @@tld_length
138
+ subdomain = options.fetch :subdomain, true
139
+ domain = options[:domain]
124
140
 
125
141
  host = ""
126
- if options[:subdomain] == true || !options.key?(:subdomain)
127
- host << extract_subdomain(options[:host], tld_length).to_param
128
- elsif options[:subdomain].present?
129
- host << options[:subdomain].to_param
142
+ if subdomain == true
143
+ return _host if domain.nil?
144
+
145
+ host << extract_subdomains_from(_host, tld_length).join('.')
146
+ elsif subdomain
147
+ host << subdomain.to_param
130
148
  end
131
149
  host << "." unless host.empty?
132
- host << (options[:domain] || extract_domain(options[:host], tld_length))
150
+ host << (domain || extract_domain_from(_host, tld_length))
133
151
  host
134
152
  end
135
153
 
136
- def normalize_port(options)
137
- return nil if options[:port].nil? || options[:port] == false
154
+ def normalize_port(port, protocol)
155
+ return unless port
138
156
 
139
- case options[:protocol]
140
- when "//"
141
- options[:port]
157
+ case protocol
158
+ when "//" then yield port
142
159
  when "https://"
143
- options[:port].to_i == 443 ? nil : options[:port]
160
+ yield port unless port.to_i == 443
144
161
  else
145
- options[:port].to_i == 80 ? nil : options[:port]
162
+ yield port unless port.to_i == 80
146
163
  end
147
164
  end
148
165
  end
@@ -165,7 +182,7 @@ module ActionDispatch
165
182
 
166
183
  # Returns the \host for this request, such as "example.com".
167
184
  def raw_host_with_port
168
- if forwarded = env["HTTP_X_FORWARDED_HOST"].presence
185
+ if forwarded = env["HTTP_X_FORWARDED_HOST"]
169
186
  forwarded.split(/,\s?/).last
170
187
  else
171
188
  env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"