actionpack 4.2.10 → 5.0.0

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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +553 -401
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/base.rb +28 -38
  6. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
  7. data/lib/abstract_controller/caching.rb +62 -0
  8. data/lib/abstract_controller/callbacks.rb +52 -19
  9. data/lib/abstract_controller/collector.rb +4 -9
  10. data/lib/abstract_controller/error.rb +4 -0
  11. data/lib/abstract_controller/helpers.rb +4 -3
  12. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  13. data/lib/abstract_controller/rendering.rb +28 -18
  14. data/lib/abstract_controller/translation.rb +8 -7
  15. data/lib/abstract_controller.rb +6 -2
  16. data/lib/action_controller/api/api_rendering.rb +14 -0
  17. data/lib/action_controller/api.rb +147 -0
  18. data/lib/action_controller/base.rb +10 -13
  19. data/lib/action_controller/caching.rb +13 -58
  20. data/lib/action_controller/form_builder.rb +48 -0
  21. data/lib/action_controller/log_subscriber.rb +3 -10
  22. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  23. data/lib/action_controller/metal/conditional_get.rb +106 -34
  24. data/lib/action_controller/metal/cookies.rb +1 -3
  25. data/lib/action_controller/metal/data_streaming.rb +11 -32
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +10 -10
  29. data/lib/action_controller/metal/head.rb +14 -8
  30. data/lib/action_controller/metal/helpers.rb +15 -6
  31. data/lib/action_controller/metal/http_authentication.rb +44 -35
  32. data/lib/action_controller/metal/implicit_render.rb +61 -6
  33. data/lib/action_controller/metal/instrumentation.rb +5 -5
  34. data/lib/action_controller/metal/live.rb +66 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +85 -40
  39. data/lib/action_controller/metal/rendering.rb +38 -6
  40. data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
  41. data/lib/action_controller/metal/rescue.rb +3 -12
  42. data/lib/action_controller/metal/streaming.rb +4 -4
  43. data/lib/action_controller/metal/strong_parameters.rb +293 -90
  44. data/lib/action_controller/metal/testing.rb +1 -12
  45. data/lib/action_controller/metal/url_for.rb +12 -5
  46. data/lib/action_controller/metal.rb +88 -63
  47. data/lib/action_controller/renderer.rb +111 -0
  48. data/lib/action_controller/template_assertions.rb +9 -0
  49. data/lib/action_controller/test_case.rb +288 -368
  50. data/lib/action_controller.rb +12 -9
  51. data/lib/action_dispatch/http/cache.rb +73 -34
  52. data/lib/action_dispatch/http/filter_parameters.rb +15 -11
  53. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  54. data/lib/action_dispatch/http/headers.rb +44 -13
  55. data/lib/action_dispatch/http/mime_negotiation.rb +41 -23
  56. data/lib/action_dispatch/http/mime_type.rb +126 -90
  57. data/lib/action_dispatch/http/mime_types.rb +3 -4
  58. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  59. data/lib/action_dispatch/http/parameters.rb +54 -41
  60. data/lib/action_dispatch/http/request.rb +149 -82
  61. data/lib/action_dispatch/http/response.rb +206 -102
  62. data/lib/action_dispatch/http/url.rb +117 -8
  63. data/lib/action_dispatch/journey/formatter.rb +39 -28
  64. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  65. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  66. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  67. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  68. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  69. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  70. data/lib/action_dispatch/journey/route.rb +74 -19
  71. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  72. data/lib/action_dispatch/journey/router.rb +5 -9
  73. data/lib/action_dispatch/journey/routes.rb +14 -15
  74. data/lib/action_dispatch/journey/visitors.rb +86 -43
  75. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  76. data/lib/action_dispatch/middleware/cookies.rb +189 -135
  77. data/lib/action_dispatch/middleware/debug_exceptions.rb +124 -49
  78. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  79. data/lib/action_dispatch/middleware/executor.rb +19 -0
  80. data/lib/action_dispatch/middleware/flash.rb +66 -45
  81. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  82. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  83. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  84. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  85. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  91. data/lib/action_dispatch/middleware/ssl.rb +115 -36
  92. data/lib/action_dispatch/middleware/stack.rb +44 -40
  93. data/lib/action_dispatch/middleware/static.rb +51 -35
  94. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  95. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  96. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  98. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  99. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  100. data/lib/action_dispatch/railtie.rb +2 -2
  101. data/lib/action_dispatch/request/session.rb +69 -33
  102. data/lib/action_dispatch/request/utils.rb +51 -19
  103. data/lib/action_dispatch/routing/inspector.rb +32 -43
  104. data/lib/action_dispatch/routing/mapper.rb +491 -338
  105. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  106. data/lib/action_dispatch/routing/redirection.rb +3 -3
  107. data/lib/action_dispatch/routing/route_set.rb +145 -238
  108. data/lib/action_dispatch/routing/url_for.rb +27 -10
  109. data/lib/action_dispatch/routing.rb +17 -13
  110. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  111. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  112. data/lib/action_dispatch/testing/assertions/routing.rb +11 -10
  113. data/lib/action_dispatch/testing/assertions.rb +1 -1
  114. data/lib/action_dispatch/testing/integration.rb +368 -97
  115. data/lib/action_dispatch/testing/test_process.rb +5 -6
  116. data/lib/action_dispatch/testing/test_request.rb +22 -31
  117. data/lib/action_dispatch/testing/test_response.rb +7 -4
  118. data/lib/action_dispatch.rb +3 -1
  119. data/lib/action_pack/gem_version.rb +3 -3
  120. data/lib/action_pack.rb +1 -1
  121. metadata +30 -34
  122. data/lib/action_controller/metal/hide_actions.rb +0 -40
  123. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  124. data/lib/action_controller/middleware.rb +0 -39
  125. data/lib/action_controller/model_naming.rb +0 -12
  126. data/lib/action_dispatch/journey/backwards.rb +0 -5
  127. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  128. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  129. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  130. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  131. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,7 +1,6 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_support/core_ext/string/filters'
3
- require 'active_support/deprecation'
4
2
  require 'action_dispatch/http/filter_redirect'
3
+ require 'action_dispatch/http/cache'
5
4
  require 'monitor'
6
5
 
7
6
  module ActionDispatch # :nodoc:
@@ -34,46 +33,62 @@ module ActionDispatch # :nodoc:
34
33
  # end
35
34
  # end
36
35
  class Response
36
+ class Header < DelegateClass(Hash) # :nodoc:
37
+ def initialize(response, header)
38
+ @response = response
39
+ super(header)
40
+ end
41
+
42
+ def []=(k,v)
43
+ if @response.sending? || @response.sent?
44
+ raise ActionDispatch::IllegalStateError, 'header already sent'
45
+ end
46
+
47
+ super
48
+ end
49
+
50
+ def merge(other)
51
+ self.class.new @response, __getobj__.merge(other)
52
+ end
53
+
54
+ def to_hash
55
+ __getobj__.dup
56
+ end
57
+ end
58
+
37
59
  # The request that the response is responding to.
38
60
  attr_accessor :request
39
61
 
40
62
  # The HTTP status code.
41
63
  attr_reader :status
42
64
 
43
- attr_writer :sending_file
65
+ # Get headers for this response.
66
+ attr_reader :header
44
67
 
45
- # Get and set headers for this response.
46
- attr_accessor :header
47
-
48
- alias_method :headers=, :header=
49
68
  alias_method :headers, :header
50
69
 
51
70
  delegate :[], :[]=, :to => :@header
52
- delegate :each, :to => :@stream
53
-
54
- # Sets the HTTP response's content MIME type. For example, in the controller
55
- # you could write this:
56
- #
57
- # response.content_type = "text/plain"
58
- #
59
- # If a character set has been defined for this response (see charset=) then
60
- # the character set information will also be included in the content type
61
- # information.
62
- attr_reader :content_type
63
71
 
64
- # The charset of the response. HTML wants to know the encoding of the
65
- # content you're giving them, so we need to send that along.
66
- attr_accessor :charset
72
+ def each(&block)
73
+ sending!
74
+ x = @stream.each(&block)
75
+ sent!
76
+ x
77
+ end
67
78
 
68
79
  CONTENT_TYPE = "Content-Type".freeze
69
80
  SET_COOKIE = "Set-Cookie".freeze
70
81
  LOCATION = "Location".freeze
71
- NO_CONTENT_CODES = [204, 304]
82
+ NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
72
83
 
73
84
  cattr_accessor(:default_charset) { "utf-8" }
74
85
  cattr_accessor(:default_headers)
75
86
 
76
87
  include Rack::Response::Helpers
88
+ # Aliasing these off because AD::Http::Cache::Response defines them
89
+ alias :_cache_control :cache_control
90
+ alias :_cache_control= :cache_control=
91
+
77
92
  include ActionDispatch::Http::FilterRedirect
78
93
  include ActionDispatch::Http::Cache::Response
79
94
  include MonitorMixin
@@ -83,20 +98,33 @@ module ActionDispatch # :nodoc:
83
98
  @response = response
84
99
  @buf = buf
85
100
  @closed = false
101
+ @str_body = nil
102
+ end
103
+
104
+ def body
105
+ @str_body ||= begin
106
+ buf = ''
107
+ each { |chunk| buf << chunk }
108
+ buf
109
+ end
86
110
  end
87
111
 
88
112
  def write(string)
89
113
  raise IOError, "closed stream" if closed?
90
114
 
115
+ @str_body = nil
91
116
  @response.commit!
92
117
  @buf.push string
93
118
  end
94
119
 
95
120
  def each(&block)
96
- @response.sending!
97
- x = @buf.each(&block)
98
- @response.sent!
99
- x
121
+ if @str_body
122
+ return enum_for(:each) unless block_given?
123
+
124
+ yield @str_body
125
+ else
126
+ each_chunk(&block)
127
+ end
100
128
  end
101
129
 
102
130
  def abort
@@ -110,39 +138,48 @@ module ActionDispatch # :nodoc:
110
138
  def closed?
111
139
  @closed
112
140
  end
141
+
142
+ private
143
+
144
+ def each_chunk(&block)
145
+ @buf.each(&block) # extract into own method
146
+ end
147
+ end
148
+
149
+ def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers)
150
+ header = merge_default_headers(header, default_headers)
151
+ new status, header, body
152
+ end
153
+
154
+ def self.merge_default_headers(original, default)
155
+ default.respond_to?(:merge) ? default.merge(original) : original
113
156
  end
114
157
 
115
158
  # The underlying body, as a streamable object.
116
159
  attr_reader :stream
117
160
 
118
- def initialize(status = 200, header = {}, body = [], options = {})
161
+ def initialize(status = 200, header = {}, body = [])
119
162
  super()
120
163
 
121
- default_headers = options.fetch(:default_headers, self.class.default_headers)
122
- header = merge_default_headers(header, default_headers)
164
+ @header = Header.new(self, header)
123
165
 
124
- self.body, self.header, self.status = body, header, status
166
+ self.body, self.status = body, status
125
167
 
126
- @sending_file = false
127
- @blank = false
128
168
  @cv = new_cond
129
169
  @committed = false
130
170
  @sending = false
131
171
  @sent = false
132
- @content_type = nil
133
- @charset = nil
134
-
135
- if content_type = self[CONTENT_TYPE]
136
- type, charset = content_type.split(/;\s*charset=/)
137
- @content_type = Mime::Type.lookup(type)
138
- @charset = charset || self.class.default_charset
139
- end
140
172
 
141
173
  prepare_cache_control!
142
174
 
143
175
  yield self if block_given?
144
176
  end
145
177
 
178
+ def has_header?(key); headers.key? key; end
179
+ def get_header(key); headers[key]; end
180
+ def set_header(key, v); headers[key] = v; end
181
+ def delete_header(key); headers.delete key; end
182
+
146
183
  def await_commit
147
184
  synchronize do
148
185
  @cv.wait_until { @committed }
@@ -187,7 +224,49 @@ module ActionDispatch # :nodoc:
187
224
 
188
225
  # Sets the HTTP content type.
189
226
  def content_type=(content_type)
190
- @content_type = content_type.to_s
227
+ header_info = parse_content_type
228
+ set_content_type content_type.to_s, header_info.charset || self.class.default_charset
229
+ end
230
+
231
+ # Sets the HTTP response's content MIME type. For example, in the controller
232
+ # you could write this:
233
+ #
234
+ # response.content_type = "text/plain"
235
+ #
236
+ # If a character set has been defined for this response (see charset=) then
237
+ # the character set information will also be included in the content type
238
+ # information.
239
+
240
+ def content_type
241
+ parse_content_type.mime_type
242
+ end
243
+
244
+ def sending_file=(v)
245
+ if true == v
246
+ self.charset = false
247
+ end
248
+ end
249
+
250
+ # Sets the HTTP character set. In case of nil parameter
251
+ # it sets the charset to utf-8.
252
+ #
253
+ # response.charset = 'utf-16' # => 'utf-16'
254
+ # response.charset = nil # => 'utf-8'
255
+ def charset=(charset)
256
+ header_info = parse_content_type
257
+ if false == charset
258
+ set_header CONTENT_TYPE, header_info.mime_type
259
+ else
260
+ content_type = header_info.mime_type
261
+ set_content_type content_type, charset || self.class.default_charset
262
+ end
263
+ end
264
+
265
+ # The charset of the response. HTML wants to know the encoding of the
266
+ # content you're giving them, so we need to send that along.
267
+ def charset
268
+ header_info = parse_content_type
269
+ header_info.charset || self.class.default_charset
191
270
  end
192
271
 
193
272
  # The response code of the request.
@@ -216,17 +295,15 @@ module ActionDispatch # :nodoc:
216
295
  # Returns the content of the response as a string. This contains the contents
217
296
  # of any calls to <tt>render</tt>.
218
297
  def body
219
- strings = []
220
- each { |part| strings << part.to_s }
221
- strings.join
298
+ @stream.body
222
299
  end
223
300
 
224
- EMPTY = " "
301
+ def write(string)
302
+ @stream.write string
303
+ end
225
304
 
226
305
  # Allows you to manually set or override the response body.
227
306
  def body=(body)
228
- @blank = true if body == EMPTY
229
-
230
307
  if body.respond_to?(:to_path)
231
308
  @stream = body
232
309
  else
@@ -236,31 +313,49 @@ module ActionDispatch # :nodoc:
236
313
  end
237
314
  end
238
315
 
239
- def body_parts
240
- parts = []
241
- @stream.each { |x| parts << x }
242
- parts
243
- end
316
+ # Avoid having to pass an open file handle as the response body.
317
+ # Rack::Sendfile will usually intercept the response and uses
318
+ # the path directly, so there is no reason to open the file.
319
+ class FileBody #:nodoc:
320
+ attr_reader :to_path
321
+
322
+ def initialize(path)
323
+ @to_path = path
324
+ end
244
325
 
245
- def set_cookie(key, value)
246
- ::Rack::Utils.set_cookie_header!(header, key, value)
326
+ def body
327
+ File.binread(to_path)
328
+ end
329
+
330
+ # Stream the file's contents if Rack::Sendfile isn't present.
331
+ def each
332
+ File.open(to_path, 'rb') do |file|
333
+ while chunk = file.read(16384)
334
+ yield chunk
335
+ end
336
+ end
337
+ end
247
338
  end
248
339
 
249
- def delete_cookie(key, value={})
250
- ::Rack::Utils.delete_cookie_header!(header, key, value)
340
+ # Send the file stored at +path+ as the response body.
341
+ def send_file(path)
342
+ commit!
343
+ @stream = FileBody.new(path)
251
344
  end
252
345
 
253
- # The location header we'll be responding with.
254
- def location
255
- headers[LOCATION]
346
+ def reset_body!
347
+ @stream = build_buffer(self, [])
256
348
  end
257
- alias_method :redirect_url, :location
258
349
 
259
- # Sets the location header we'll be responding with.
260
- def location=(url)
261
- headers[LOCATION] = url
350
+ def body_parts
351
+ parts = []
352
+ @stream.each { |x| parts << x }
353
+ parts
262
354
  end
263
355
 
356
+ # The location header we'll be responding with.
357
+ alias_method :redirect_url, :location
358
+
264
359
  def close
265
360
  stream.close if stream.respond_to?(:close)
266
361
  end
@@ -277,34 +372,21 @@ module ActionDispatch # :nodoc:
277
372
  end
278
373
 
279
374
  # Turns the Response into a Rack-compatible array of the status, headers,
280
- # and body. Allows explict splatting:
375
+ # and body. Allows explicit splatting:
281
376
  #
282
377
  # status, headers, body = *response
283
378
  def to_a
379
+ commit!
284
380
  rack_response @status, @header.to_hash
285
381
  end
286
382
  alias prepare! to_a
287
383
 
288
- # Be super clear that a response object is not an Array. Defining this
289
- # would make implicit splatting work, but it also makes adding responses
290
- # as arrays work, and "flattening" responses, cascading to the rack body!
291
- # Not sensible behavior.
292
- def to_ary
293
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
294
- `ActionDispatch::Response#to_ary` no longer performs implicit conversion
295
- to an array. Please use `response.to_a` instead, or a splat like `status,
296
- headers, body = *response`.
297
- MSG
298
-
299
- to_a
300
- end
301
-
302
384
  # Returns the response cookies, converted to a Hash of (name => value) pairs
303
385
  #
304
386
  # assert_equal 'AuthorOfNewPage', r.cookies['author']
305
387
  def cookies
306
388
  cookies = {}
307
- if header = self[SET_COOKIE]
389
+ if header = get_header(SET_COOKIE)
308
390
  header = header.split("\n") if header.respond_to?(:to_str)
309
391
  header.each do |cookie|
310
392
  if pair = cookie.split(';').first
@@ -318,14 +400,43 @@ module ActionDispatch # :nodoc:
318
400
 
319
401
  private
320
402
 
403
+ ContentTypeHeader = Struct.new :mime_type, :charset
404
+ NullContentTypeHeader = ContentTypeHeader.new nil, nil
405
+
406
+ def parse_content_type
407
+ content_type = get_header CONTENT_TYPE
408
+ if content_type
409
+ type, charset = content_type.split(/;\s*charset=/)
410
+ type = nil if type.empty?
411
+ ContentTypeHeader.new(type, charset)
412
+ else
413
+ NullContentTypeHeader
414
+ end
415
+ end
416
+
417
+ def set_content_type(content_type, charset)
418
+ type = (content_type || '').dup
419
+ type << "; charset=#{charset}" if charset
420
+ set_header CONTENT_TYPE, type
421
+ end
422
+
321
423
  def before_committed
424
+ return if committed?
425
+ assign_default_content_type_and_charset!
426
+ handle_conditional_get!
427
+ handle_no_content!
322
428
  end
323
429
 
324
430
  def before_sending
325
- end
431
+ # Normally we've already committed by now, but it's possible
432
+ # (e.g., if the controller action tries to read back its own
433
+ # response) to get here before that. In that case, we must force
434
+ # an "early" commit: we're about to freeze the headers, so this is
435
+ # our last chance.
436
+ commit! unless committed?
326
437
 
327
- def merge_default_headers(original, default)
328
- default.respond_to?(:merge) ? default.merge(original) : original
438
+ headers.freeze
439
+ request.commit_cookie_jar! unless committed?
329
440
  end
330
441
 
331
442
  def build_buffer(response, body)
@@ -336,20 +447,12 @@ module ActionDispatch # :nodoc:
336
447
  body.respond_to?(:each) ? body : [body]
337
448
  end
338
449
 
339
- def assign_default_content_type_and_charset!(headers)
340
- return if headers[CONTENT_TYPE].present?
341
-
342
- @content_type ||= Mime::HTML
343
- @charset ||= self.class.default_charset unless @charset == false
450
+ def assign_default_content_type_and_charset!
451
+ return if content_type
344
452
 
345
- type = @content_type.to_s.dup
346
- type << "; charset=#{@charset}" if append_charset?
347
-
348
- headers[CONTENT_TYPE] = type
349
- end
350
-
351
- def append_charset?
352
- !@sending_file && @charset != false
453
+ ct = parse_content_type
454
+ set_content_type(ct.mime_type || Mime[:html].to_s,
455
+ ct.charset || self.class.default_charset)
353
456
  end
354
457
 
355
458
  class RackBody
@@ -388,14 +491,15 @@ module ActionDispatch # :nodoc:
388
491
  end
389
492
  end
390
493
 
391
- def rack_response(status, header)
392
- assign_default_content_type_and_charset!(header)
393
- handle_conditional_get!
394
-
395
- header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
396
-
494
+ def handle_no_content!
397
495
  if NO_CONTENT_CODES.include?(@status)
398
- header.delete CONTENT_TYPE
496
+ @header.delete CONTENT_TYPE
497
+ @header.delete 'Content-Length'
498
+ end
499
+ end
500
+
501
+ def rack_response(status, header)
502
+ if NO_CONTENT_CODES.include?(status)
399
503
  [status, header, []]
400
504
  else
401
505
  [status, header, RackBody.new(self)]
@@ -1,5 +1,4 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_support/core_ext/hash/slice'
3
2
 
4
3
  module ActionDispatch
5
4
  module Http
@@ -12,10 +11,22 @@ module ActionDispatch
12
11
  self.tld_length = 1
13
12
 
14
13
  class << self
14
+ # Returns the domain part of a host given the domain level.
15
+ #
16
+ # # Top-level domain example
17
+ # extract_domain('www.example.com', 1) # => "example.com"
18
+ # # Second-level domain example
19
+ # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk"
15
20
  def extract_domain(host, tld_length)
16
21
  extract_domain_from(host, tld_length) if named_host?(host)
17
22
  end
18
23
 
24
+ # Returns the subdomains of a host as an Array given the domain level.
25
+ #
26
+ # # Top-level domain example
27
+ # extract_subdomains('www.example.com', 1) # => ["www"]
28
+ # # Second-level domain example
29
+ # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"]
19
30
  def extract_subdomains(host, tld_length)
20
31
  if named_host?(host)
21
32
  extract_subdomains_from(host, tld_length)
@@ -24,6 +35,12 @@ module ActionDispatch
24
35
  end
25
36
  end
26
37
 
38
+ # Returns the subdomains of a host as a String given the domain level.
39
+ #
40
+ # # Top-level domain example
41
+ # extract_subdomain('www.example.com', 1) # => "www"
42
+ # # Second-level domain example
43
+ # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www"
27
44
  def extract_subdomain(host, tld_length)
28
45
  extract_subdomains(host, tld_length).join('.')
29
46
  end
@@ -49,7 +66,7 @@ module ActionDispatch
49
66
  end
50
67
 
51
68
  def path_for(options)
52
- path = options[:script_name].to_s.chomp("/")
69
+ path = options[:script_name].to_s.chomp("/".freeze)
53
70
  path << options[:path] if options.key?(:path)
54
71
 
55
72
  add_trailing_slash(path) if options[:trailing_slash]
@@ -64,7 +81,8 @@ module ActionDispatch
64
81
  def add_params(path, params)
65
82
  params = { params: params } unless params.is_a?(Hash)
66
83
  params.reject! { |_,v| v.to_param.nil? }
67
- path << "?#{params.to_query}" unless params.empty?
84
+ query = params.to_query
85
+ path << "?#{query}" unless query.empty?
68
86
  end
69
87
 
70
88
  def add_anchor(path, anchor)
@@ -166,43 +184,97 @@ module ActionDispatch
166
184
  end
167
185
  end
168
186
 
169
- def initialize(env)
187
+ def initialize
170
188
  super
171
189
  @protocol = nil
172
190
  @port = nil
173
191
  end
174
192
 
175
193
  # Returns the complete URL used for this request.
194
+ #
195
+ # class Request < Rack::Request
196
+ # include ActionDispatch::Http::URL
197
+ # end
198
+ #
199
+ # req = Request.new 'HTTP_HOST' => 'example.com'
200
+ # req.url # => "http://example.com"
176
201
  def url
177
202
  protocol + host_with_port + fullpath
178
203
  end
179
204
 
180
205
  # Returns 'https://' if this is an SSL request and 'http://' otherwise.
206
+ #
207
+ # class Request < Rack::Request
208
+ # include ActionDispatch::Http::URL
209
+ # end
210
+ #
211
+ # req = Request.new 'HTTP_HOST' => 'example.com'
212
+ # req.protocol # => "http://"
213
+ #
214
+ # req = Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on'
215
+ # req.protocol # => "https://"
181
216
  def protocol
182
217
  @protocol ||= ssl? ? 'https://' : 'http://'
183
218
  end
184
219
 
185
220
  # Returns the \host for this request, such as "example.com".
221
+ #
222
+ # class Request < Rack::Request
223
+ # include ActionDispatch::Http::URL
224
+ # end
225
+ #
226
+ # req = Request.new 'HTTP_HOST' => 'example.com'
227
+ # req.raw_host_with_port # => "example.com"
228
+ #
229
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
230
+ # req.raw_host_with_port # => "example.com:8080"
186
231
  def raw_host_with_port
187
- if forwarded = env["HTTP_X_FORWARDED_HOST"].presence
232
+ if forwarded = x_forwarded_host.presence
188
233
  forwarded.split(/,\s?/).last
189
234
  else
190
- env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
235
+ get_header('HTTP_HOST') || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}"
191
236
  end
192
237
  end
193
238
 
194
239
  # Returns the host for this request, such as example.com.
240
+ #
241
+ # class Request < Rack::Request
242
+ # include ActionDispatch::Http::URL
243
+ # end
244
+ #
245
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
246
+ # req.host # => "example.com"
195
247
  def host
196
- raw_host_with_port.sub(/:\d+$/, '')
248
+ raw_host_with_port.sub(/:\d+$/, ''.freeze)
197
249
  end
198
250
 
199
251
  # Returns a \host:\port string for this request, such as "example.com" or
200
252
  # "example.com:8080".
253
+ #
254
+ # class Request < Rack::Request
255
+ # include ActionDispatch::Http::URL
256
+ # end
257
+ #
258
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
259
+ # req.host_with_port # => "example.com"
260
+ #
261
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
262
+ # req.host_with_port # => "example.com:8080"
201
263
  def host_with_port
202
264
  "#{host}#{port_string}"
203
265
  end
204
266
 
205
267
  # Returns the port number of this request as an integer.
268
+ #
269
+ # class Request < Rack::Request
270
+ # include ActionDispatch::Http::URL
271
+ # end
272
+ #
273
+ # req = Request.new 'HTTP_HOST' => 'example.com'
274
+ # req.port # => 80
275
+ #
276
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
277
+ # req.port # => 8080
206
278
  def port
207
279
  @port ||= begin
208
280
  if raw_host_with_port =~ /:(\d+)$/
@@ -214,6 +286,13 @@ module ActionDispatch
214
286
  end
215
287
 
216
288
  # Returns the standard \port number for this request's protocol.
289
+ #
290
+ # class Request < Rack::Request
291
+ # include ActionDispatch::Http::URL
292
+ # end
293
+ #
294
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
295
+ # req.standard_port # => 80
217
296
  def standard_port
218
297
  case protocol
219
298
  when 'https://' then 443
@@ -222,24 +301,54 @@ module ActionDispatch
222
301
  end
223
302
 
224
303
  # Returns whether this request is using the standard port
304
+ #
305
+ # class Request < Rack::Request
306
+ # include ActionDispatch::Http::URL
307
+ # end
308
+ #
309
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
310
+ # req.standard_port? # => true
311
+ #
312
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
313
+ # req.standard_port? # => false
225
314
  def standard_port?
226
315
  port == standard_port
227
316
  end
228
317
 
229
318
  # Returns a number \port suffix like 8080 if the \port number of this request
230
319
  # is not the default HTTP \port 80 or HTTPS \port 443.
320
+ #
321
+ # class Request < Rack::Request
322
+ # include ActionDispatch::Http::URL
323
+ # end
324
+ #
325
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
326
+ # req.optional_port # => nil
327
+ #
328
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
329
+ # req.optional_port # => 8080
231
330
  def optional_port
232
331
  standard_port? ? nil : port
233
332
  end
234
333
 
235
334
  # Returns a string \port suffix, including colon, like ":8080" if the \port
236
335
  # number of this request is not the default HTTP \port 80 or HTTPS \port 443.
336
+ #
337
+ # class Request < Rack::Request
338
+ # include ActionDispatch::Http::URL
339
+ # end
340
+ #
341
+ # req = Request.new 'HTTP_HOST' => 'example.com:80'
342
+ # req.port_string # => ""
343
+ #
344
+ # req = Request.new 'HTTP_HOST' => 'example.com:8080'
345
+ # req.port_string # => ":8080"
237
346
  def port_string
238
347
  standard_port? ? '' : ":#{port}"
239
348
  end
240
349
 
241
350
  def server_port
242
- @env['SERVER_PORT'].to_i
351
+ get_header('SERVER_PORT').to_i
243
352
  end
244
353
 
245
354
  # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify