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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +553 -401
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller/base.rb +28 -38
- data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
- data/lib/abstract_controller/caching.rb +62 -0
- data/lib/abstract_controller/callbacks.rb +52 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/error.rb +4 -0
- data/lib/abstract_controller/helpers.rb +4 -3
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +28 -18
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/abstract_controller.rb +6 -2
- data/lib/action_controller/api/api_rendering.rb +14 -0
- data/lib/action_controller/api.rb +147 -0
- data/lib/action_controller/base.rb +10 -13
- data/lib/action_controller/caching.rb +13 -58
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +3 -10
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +106 -34
- data/lib/action_controller/metal/cookies.rb +1 -3
- data/lib/action_controller/metal/data_streaming.rb +11 -32
- data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +10 -10
- data/lib/action_controller/metal/head.rb +14 -8
- data/lib/action_controller/metal/helpers.rb +15 -6
- data/lib/action_controller/metal/http_authentication.rb +44 -35
- data/lib/action_controller/metal/implicit_render.rb +61 -6
- data/lib/action_controller/metal/instrumentation.rb +5 -5
- data/lib/action_controller/metal/live.rb +66 -88
- data/lib/action_controller/metal/mime_responds.rb +27 -42
- data/lib/action_controller/metal/params_wrapper.rb +8 -8
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +85 -40
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
- data/lib/action_controller/metal/rescue.rb +3 -12
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +293 -90
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/metal.rb +88 -63
- data/lib/action_controller/renderer.rb +111 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +288 -368
- data/lib/action_controller.rb +12 -9
- data/lib/action_dispatch/http/cache.rb +73 -34
- data/lib/action_dispatch/http/filter_parameters.rb +15 -11
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +44 -13
- data/lib/action_dispatch/http/mime_negotiation.rb +41 -23
- data/lib/action_dispatch/http/mime_type.rb +126 -90
- data/lib/action_dispatch/http/mime_types.rb +3 -4
- data/lib/action_dispatch/http/parameter_filter.rb +18 -8
- data/lib/action_dispatch/http/parameters.rb +54 -41
- data/lib/action_dispatch/http/request.rb +149 -82
- data/lib/action_dispatch/http/response.rb +206 -102
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +39 -28
- data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
- data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
- data/lib/action_dispatch/journey/nodes/node.rb +14 -4
- data/lib/action_dispatch/journey/parser_extras.rb +4 -0
- data/lib/action_dispatch/journey/path/pattern.rb +38 -42
- data/lib/action_dispatch/journey/route.rb +74 -19
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/router.rb +5 -9
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +86 -43
- data/lib/action_dispatch/middleware/callbacks.rb +10 -1
- data/lib/action_dispatch/middleware/cookies.rb +189 -135
- data/lib/action_dispatch/middleware/debug_exceptions.rb +124 -49
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
- data/lib/action_dispatch/middleware/executor.rb +19 -0
- data/lib/action_dispatch/middleware/flash.rb +66 -45
- data/lib/action_dispatch/middleware/params_parser.rb +32 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +14 -58
- data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
- data/lib/action_dispatch/middleware/request_id.rb +11 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
- data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
- data/lib/action_dispatch/middleware/ssl.rb +115 -36
- data/lib/action_dispatch/middleware/stack.rb +44 -40
- data/lib/action_dispatch/middleware/static.rb +51 -35
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
- data/lib/action_dispatch/railtie.rb +2 -2
- data/lib/action_dispatch/request/session.rb +69 -33
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing/inspector.rb +32 -43
- data/lib/action_dispatch/routing/mapper.rb +491 -338
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +145 -238
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/routing.rb +17 -13
- data/lib/action_dispatch/testing/assertion_response.rb +45 -0
- data/lib/action_dispatch/testing/assertions/response.rb +38 -20
- data/lib/action_dispatch/testing/assertions/routing.rb +11 -10
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/integration.rb +368 -97
- data/lib/action_dispatch/testing/test_process.rb +5 -6
- data/lib/action_dispatch/testing/test_request.rb +22 -31
- data/lib/action_dispatch/testing/test_response.rb +7 -4
- data/lib/action_dispatch.rb +3 -1
- data/lib/action_pack/gem_version.rb +3 -3
- data/lib/action_pack.rb +1 -1
- metadata +30 -34
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
- /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
data/lib/action_controller.rb
CHANGED
@@ -7,13 +7,18 @@ require 'action_controller/metal/strong_parameters'
|
|
7
7
|
module ActionController
|
8
8
|
extend ActiveSupport::Autoload
|
9
9
|
|
10
|
+
autoload :API
|
10
11
|
autoload :Base
|
11
|
-
autoload :Caching
|
12
12
|
autoload :Metal
|
13
13
|
autoload :Middleware
|
14
|
+
autoload :Renderer
|
15
|
+
autoload :FormBuilder
|
16
|
+
|
17
|
+
eager_autoload do
|
18
|
+
autoload :Caching
|
19
|
+
end
|
14
20
|
|
15
21
|
autoload_under "metal" do
|
16
|
-
autoload :Compatibility
|
17
22
|
autoload :ConditionalGet
|
18
23
|
autoload :Cookies
|
19
24
|
autoload :DataStreaming
|
@@ -22,13 +27,12 @@ module ActionController
|
|
22
27
|
autoload :ForceSSL
|
23
28
|
autoload :Head
|
24
29
|
autoload :Helpers
|
25
|
-
autoload :HideActions
|
26
30
|
autoload :HttpAuthentication
|
31
|
+
autoload :BasicImplicitRender
|
27
32
|
autoload :ImplicitRender
|
28
33
|
autoload :Instrumentation
|
29
34
|
autoload :MimeResponds
|
30
35
|
autoload :ParamsWrapper
|
31
|
-
autoload :RackDelegation
|
32
36
|
autoload :Redirecting
|
33
37
|
autoload :Renderers
|
34
38
|
autoload :Rendering
|
@@ -40,13 +44,12 @@ module ActionController
|
|
40
44
|
autoload :UrlFor
|
41
45
|
end
|
42
46
|
|
47
|
+
autoload_under "api" do
|
48
|
+
autoload :ApiRendering
|
49
|
+
end
|
50
|
+
|
43
51
|
autoload :TestCase, 'action_controller/test_case'
|
44
52
|
autoload :TemplateAssertions, 'action_controller/test_case'
|
45
|
-
|
46
|
-
def self.eager_load!
|
47
|
-
super
|
48
|
-
ActionController::Caching.eager_load!
|
49
|
-
end
|
50
53
|
end
|
51
54
|
|
52
55
|
# Common Active Support usage in Action Controller
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module ActionDispatch
|
3
2
|
module Http
|
4
3
|
module Cache
|
@@ -8,19 +7,17 @@ module ActionDispatch
|
|
8
7
|
HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
|
9
8
|
|
10
9
|
def if_modified_since
|
11
|
-
if since =
|
10
|
+
if since = get_header(HTTP_IF_MODIFIED_SINCE)
|
12
11
|
Time.rfc2822(since) rescue nil
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
16
15
|
def if_none_match
|
17
|
-
|
16
|
+
get_header HTTP_IF_NONE_MATCH
|
18
17
|
end
|
19
18
|
|
20
19
|
def if_none_match_etags
|
21
|
-
|
22
|
-
etag.gsub(/^\"|\"$/, "")
|
23
|
-
end
|
20
|
+
if_none_match ? if_none_match.split(/\s*,\s*/) : []
|
24
21
|
end
|
25
22
|
|
26
23
|
def not_modified?(modified_at)
|
@@ -29,8 +26,8 @@ module ActionDispatch
|
|
29
26
|
|
30
27
|
def etag_matches?(etag)
|
31
28
|
if etag
|
32
|
-
|
33
|
-
|
29
|
+
validators = if_none_match_etags
|
30
|
+
validators.include?(etag) || validators.include?('*')
|
34
31
|
end
|
35
32
|
end
|
36
33
|
|
@@ -51,52 +48,95 @@ module ActionDispatch
|
|
51
48
|
end
|
52
49
|
|
53
50
|
module Response
|
54
|
-
attr_reader :cache_control
|
55
|
-
alias :etag? :etag
|
51
|
+
attr_reader :cache_control
|
56
52
|
|
57
53
|
def last_modified
|
58
|
-
if last =
|
54
|
+
if last = get_header(LAST_MODIFIED)
|
59
55
|
Time.httpdate(last)
|
60
56
|
end
|
61
57
|
end
|
62
58
|
|
63
59
|
def last_modified?
|
64
|
-
|
60
|
+
has_header? LAST_MODIFIED
|
65
61
|
end
|
66
62
|
|
67
63
|
def last_modified=(utc_time)
|
68
|
-
|
64
|
+
set_header LAST_MODIFIED, utc_time.httpdate
|
69
65
|
end
|
70
66
|
|
71
67
|
def date
|
72
|
-
if date_header =
|
68
|
+
if date_header = get_header(DATE)
|
73
69
|
Time.httpdate(date_header)
|
74
70
|
end
|
75
71
|
end
|
76
72
|
|
77
73
|
def date?
|
78
|
-
|
74
|
+
has_header? DATE
|
79
75
|
end
|
80
76
|
|
81
77
|
def date=(utc_time)
|
82
|
-
|
78
|
+
set_header DATE, utc_time.httpdate
|
79
|
+
end
|
80
|
+
|
81
|
+
# This method sets a weak ETag validator on the response so browsers
|
82
|
+
# and proxies may cache the response, keyed on the ETag. On subsequent
|
83
|
+
# requests, the If-None-Match header is set to the cached ETag. If it
|
84
|
+
# matches the current ETag, we can return a 304 Not Modified response
|
85
|
+
# with no body, letting the browser or proxy know that their cache is
|
86
|
+
# current. Big savings in request time and network bandwidth.
|
87
|
+
#
|
88
|
+
# Weak ETags are considered to be semantically equivalent but not
|
89
|
+
# byte-for-byte identical. This is perfect for browser caching of HTML
|
90
|
+
# pages where we don't care about exact equality, just what the user
|
91
|
+
# is viewing.
|
92
|
+
#
|
93
|
+
# Strong ETags are considered byte-for-byte identical. They allow a
|
94
|
+
# browser or proxy cache to support Range requests, useful for paging
|
95
|
+
# through a PDF file or scrubbing through a video. Some CDNs only
|
96
|
+
# support strong ETags and will ignore weak ETags entirely.
|
97
|
+
#
|
98
|
+
# Weak ETags are what we almost always need, so they're the default.
|
99
|
+
# Check out `#strong_etag=` to provide a strong ETag validator.
|
100
|
+
def etag=(weak_validators)
|
101
|
+
self.weak_etag = weak_validators
|
83
102
|
end
|
84
103
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
104
|
+
def weak_etag=(weak_validators)
|
105
|
+
set_header 'ETag', generate_weak_etag(weak_validators)
|
106
|
+
end
|
107
|
+
|
108
|
+
def strong_etag=(strong_validators)
|
109
|
+
set_header 'ETag', generate_strong_etag(strong_validators)
|
110
|
+
end
|
111
|
+
|
112
|
+
def etag?; etag; end
|
113
|
+
|
114
|
+
# True if an ETag is set and it's a weak validator (preceded with W/)
|
115
|
+
def weak_etag?
|
116
|
+
etag? && etag.starts_with?('W/"')
|
117
|
+
end
|
118
|
+
|
119
|
+
# True if an ETag is set and it isn't a weak validator (not preceded with W/)
|
120
|
+
def strong_etag?
|
121
|
+
etag? && !weak_etag?
|
88
122
|
end
|
89
123
|
|
90
124
|
private
|
91
125
|
|
92
126
|
DATE = 'Date'.freeze
|
93
127
|
LAST_MODIFIED = "Last-Modified".freeze
|
94
|
-
|
95
|
-
|
96
|
-
|
128
|
+
SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
|
129
|
+
|
130
|
+
def generate_weak_etag(validators)
|
131
|
+
"W/#{generate_strong_etag(validators)}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def generate_strong_etag(validators)
|
135
|
+
%("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
|
136
|
+
end
|
97
137
|
|
98
138
|
def cache_control_segments
|
99
|
-
if cache_control =
|
139
|
+
if cache_control = _cache_control
|
100
140
|
cache_control.delete(' ').split(',')
|
101
141
|
else
|
102
142
|
[]
|
@@ -123,12 +163,11 @@ module ActionDispatch
|
|
123
163
|
|
124
164
|
def prepare_cache_control!
|
125
165
|
@cache_control = cache_control_headers
|
126
|
-
@etag = self[ETAG]
|
127
166
|
end
|
128
167
|
|
129
168
|
def handle_conditional_get!
|
130
169
|
if etag? || last_modified? || !@cache_control.empty?
|
131
|
-
set_conditional_cache_control!
|
170
|
+
set_conditional_cache_control!(@cache_control)
|
132
171
|
end
|
133
172
|
end
|
134
173
|
|
@@ -138,24 +177,24 @@ module ActionDispatch
|
|
138
177
|
PRIVATE = "private".freeze
|
139
178
|
MUST_REVALIDATE = "must-revalidate".freeze
|
140
179
|
|
141
|
-
def set_conditional_cache_control!
|
180
|
+
def set_conditional_cache_control!(cache_control)
|
142
181
|
control = {}
|
143
182
|
cc_headers = cache_control_headers
|
144
183
|
if extras = cc_headers.delete(:extras)
|
145
|
-
|
146
|
-
|
147
|
-
|
184
|
+
cache_control[:extras] ||= []
|
185
|
+
cache_control[:extras] += extras
|
186
|
+
cache_control[:extras].uniq!
|
148
187
|
end
|
149
188
|
|
150
189
|
control.merge! cc_headers
|
151
|
-
control.merge!
|
190
|
+
control.merge! cache_control
|
152
191
|
|
153
192
|
if control.empty?
|
154
|
-
|
193
|
+
self._cache_control = DEFAULT_CACHE_CONTROL
|
155
194
|
elsif control[:no_cache]
|
156
|
-
|
195
|
+
self._cache_control = NO_CACHE
|
157
196
|
if control[:extras]
|
158
|
-
|
197
|
+
self._cache_control = _cache_control + ", #{control[:extras].join(', ')}"
|
159
198
|
end
|
160
199
|
else
|
161
200
|
extras = control[:extras]
|
@@ -167,7 +206,7 @@ module ActionDispatch
|
|
167
206
|
options << MUST_REVALIDATE if control[:must_revalidate]
|
168
207
|
options.concat(extras) if extras
|
169
208
|
|
170
|
-
|
209
|
+
self._cache_control = options.join(", ")
|
171
210
|
end
|
172
211
|
end
|
173
212
|
end
|
@@ -1,14 +1,14 @@
|
|
1
|
-
require 'active_support/core_ext/hash/keys'
|
2
|
-
require 'active_support/core_ext/object/duplicable'
|
3
1
|
require 'action_dispatch/http/parameter_filter'
|
4
2
|
|
5
3
|
module ActionDispatch
|
6
4
|
module Http
|
7
5
|
# Allows you to specify sensitive parameters which will be replaced from
|
8
6
|
# the request log by looking in the query string of the request and all
|
9
|
-
# sub-hashes of the params hash to filter.
|
10
|
-
#
|
11
|
-
#
|
7
|
+
# sub-hashes of the params hash to filter. Filtering only certain sub-keys
|
8
|
+
# from a hash is possible by using the dot notation: 'credit_card.number'.
|
9
|
+
# If a block is given, each key and value of the params hash and all
|
10
|
+
# sub-hashes is passed to it, the value or key can be replaced using
|
11
|
+
# String#replace or similar method.
|
12
12
|
#
|
13
13
|
# env["action_dispatch.parameter_filter"] = [:password]
|
14
14
|
# => replaces the value to all keys matching /password/i with "[FILTERED]"
|
@@ -16,7 +16,11 @@ module ActionDispatch
|
|
16
16
|
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
17
17
|
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
|
18
18
|
#
|
19
|
-
# env["action_dispatch.parameter_filter"] =
|
19
|
+
# env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
|
20
|
+
# => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
|
21
|
+
# change { file: { code: "xxxx"} }
|
22
|
+
#
|
23
|
+
# env["action_dispatch.parameter_filter"] = -> (k, v) do
|
20
24
|
# v.reverse! if k =~ /secret/i
|
21
25
|
# end
|
22
26
|
# => reverses the value to all keys matching /secret/i
|
@@ -25,19 +29,19 @@ module ActionDispatch
|
|
25
29
|
NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
|
26
30
|
NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
|
27
31
|
|
28
|
-
def initialize
|
32
|
+
def initialize
|
29
33
|
super
|
30
34
|
@filtered_parameters = nil
|
31
35
|
@filtered_env = nil
|
32
36
|
@filtered_path = nil
|
33
37
|
end
|
34
38
|
|
35
|
-
#
|
39
|
+
# Returns a hash of parameters with all sensitive data replaced.
|
36
40
|
def filtered_parameters
|
37
41
|
@filtered_parameters ||= parameter_filter.filter(parameters)
|
38
42
|
end
|
39
43
|
|
40
|
-
#
|
44
|
+
# Returns a hash of request.env with all sensitive data replaced.
|
41
45
|
def filtered_env
|
42
46
|
@filtered_env ||= env_filter.filter(@env)
|
43
47
|
end
|
@@ -50,13 +54,13 @@ module ActionDispatch
|
|
50
54
|
protected
|
51
55
|
|
52
56
|
def parameter_filter
|
53
|
-
parameter_filter_for
|
57
|
+
parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
|
54
58
|
return NULL_PARAM_FILTER
|
55
59
|
}
|
56
60
|
end
|
57
61
|
|
58
62
|
def env_filter
|
59
|
-
user_key =
|
63
|
+
user_key = fetch_header("action_dispatch.parameter_filter") {
|
60
64
|
return NULL_ENV_FILTER
|
61
65
|
}
|
62
66
|
parameter_filter_for(Array(user_key) + ENV_MATCH)
|
@@ -4,9 +4,8 @@ module ActionDispatch
|
|
4
4
|
|
5
5
|
FILTERED = '[FILTERED]'.freeze # :nodoc:
|
6
6
|
|
7
|
-
def filtered_location
|
8
|
-
|
9
|
-
if !filters.empty? && location_filter_match?(filters)
|
7
|
+
def filtered_location # :nodoc:
|
8
|
+
if location_filter_match?
|
10
9
|
FILTERED
|
11
10
|
else
|
12
11
|
location
|
@@ -15,20 +14,20 @@ module ActionDispatch
|
|
15
14
|
|
16
15
|
private
|
17
16
|
|
18
|
-
def
|
17
|
+
def location_filters
|
19
18
|
if request
|
20
|
-
request.
|
19
|
+
request.get_header('action_dispatch.redirect_filter') || []
|
21
20
|
else
|
22
21
|
[]
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
26
|
-
def location_filter_match?
|
27
|
-
|
25
|
+
def location_filter_match?
|
26
|
+
location_filters.any? do |filter|
|
28
27
|
if String === filter
|
29
28
|
location.include?(filter)
|
30
29
|
elsif Regexp === filter
|
31
|
-
location
|
30
|
+
location =~ filter
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
@@ -2,9 +2,23 @@ module ActionDispatch
|
|
2
2
|
module Http
|
3
3
|
# Provides access to the request's HTTP headers from the environment.
|
4
4
|
#
|
5
|
-
# env = { "CONTENT_TYPE" => "text/plain" }
|
5
|
+
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
6
6
|
# headers = ActionDispatch::Http::Headers.new(env)
|
7
7
|
# headers["Content-Type"] # => "text/plain"
|
8
|
+
# headers["User-Agent"] # => "curl/7.43.0"
|
9
|
+
#
|
10
|
+
# Also note that when headers are mapped to CGI-like variables by the Rack
|
11
|
+
# server, both dashes and underscores are converted to underscores. This
|
12
|
+
# ambiguity cannot be resolved at this stage anymore. Both underscores and
|
13
|
+
# dashes have to be interpreted as if they were originally sent as dashes.
|
14
|
+
#
|
15
|
+
# # GET / HTTP/1.1
|
16
|
+
# # ...
|
17
|
+
# # User-Agent: curl/7.43.0
|
18
|
+
# # X_Custom_Header: token
|
19
|
+
#
|
20
|
+
# headers["X_Custom_Header"] # => nil
|
21
|
+
# headers["X-Custom-Header"] # => "token"
|
8
22
|
class Headers
|
9
23
|
CGI_VARIABLES = Set.new(%W[
|
10
24
|
AUTH_TYPE
|
@@ -30,27 +44,37 @@ module ActionDispatch
|
|
30
44
|
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
|
31
45
|
|
32
46
|
include Enumerable
|
33
|
-
attr_reader :env
|
34
47
|
|
35
|
-
def
|
36
|
-
|
48
|
+
def self.from_hash(hash)
|
49
|
+
new ActionDispatch::Request.new hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(request) # :nodoc:
|
53
|
+
@req = request
|
37
54
|
end
|
38
55
|
|
39
56
|
# Returns the value for the given key mapped to @env.
|
40
57
|
def [](key)
|
41
|
-
@
|
58
|
+
@req.get_header env_name(key)
|
42
59
|
end
|
43
60
|
|
44
61
|
# Sets the given value for the key mapped to @env.
|
45
62
|
def []=(key, value)
|
46
|
-
@
|
63
|
+
@req.set_header env_name(key), value
|
64
|
+
end
|
65
|
+
|
66
|
+
# Add a value to a multivalued header like Vary or Accept-Encoding.
|
67
|
+
def add(key, value)
|
68
|
+
@req.add_header env_name(key), value
|
47
69
|
end
|
48
70
|
|
49
71
|
def key?(key)
|
50
|
-
@
|
72
|
+
@req.has_header? env_name(key)
|
51
73
|
end
|
52
74
|
alias :include? :key?
|
53
75
|
|
76
|
+
DEFAULT = Object.new # :nodoc:
|
77
|
+
|
54
78
|
# Returns the value for the given key mapped to @env.
|
55
79
|
#
|
56
80
|
# If the key is not found and an optional code block is not provided,
|
@@ -58,18 +82,22 @@ module ActionDispatch
|
|
58
82
|
#
|
59
83
|
# If the code block is provided, then it will be run and
|
60
84
|
# its result returned.
|
61
|
-
def fetch(key,
|
62
|
-
@
|
85
|
+
def fetch(key, default = DEFAULT)
|
86
|
+
@req.fetch_header(env_name(key)) do
|
87
|
+
return default unless default == DEFAULT
|
88
|
+
return yield if block_given?
|
89
|
+
raise NameError, key
|
90
|
+
end
|
63
91
|
end
|
64
92
|
|
65
93
|
def each(&block)
|
66
|
-
@
|
94
|
+
@req.each_header(&block)
|
67
95
|
end
|
68
96
|
|
69
97
|
# Returns a new Http::Headers instance containing the contents of
|
70
98
|
# <tt>headers_or_env</tt> and the original instance.
|
71
99
|
def merge(headers_or_env)
|
72
|
-
headers =
|
100
|
+
headers = @req.dup.headers
|
73
101
|
headers.merge!(headers_or_env)
|
74
102
|
headers
|
75
103
|
end
|
@@ -79,12 +107,15 @@ module ActionDispatch
|
|
79
107
|
# <tt>headers_or_env</tt>.
|
80
108
|
def merge!(headers_or_env)
|
81
109
|
headers_or_env.each do |key, value|
|
82
|
-
|
110
|
+
@req.set_header env_name(key), value
|
83
111
|
end
|
84
112
|
end
|
85
113
|
|
114
|
+
def env; @req.env.dup; end
|
115
|
+
|
86
116
|
private
|
87
|
-
|
117
|
+
|
118
|
+
# Converts an HTTP header name to an environment variable name if it is
|
88
119
|
# not contained within the headers hash.
|
89
120
|
def env_name(key)
|
90
121
|
key = key.to_s
|
@@ -10,19 +10,18 @@ module ActionDispatch
|
|
10
10
|
self.ignore_accept_header = false
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
# The MIME type of the HTTP request, such as Mime::XML.
|
13
|
+
# The MIME type of the HTTP request, such as Mime[:xml].
|
16
14
|
#
|
17
15
|
# For backward compatibility, the post \format is extracted from the
|
18
16
|
# X-Post-Data-Format HTTP header if present.
|
19
17
|
def content_mime_type
|
20
|
-
|
21
|
-
if
|
18
|
+
fetch_header("action_dispatch.request.content_type") do |k|
|
19
|
+
v = if get_header('CONTENT_TYPE') =~ /^([^,\;]*)/
|
22
20
|
Mime::Type.lookup($1.strip.downcase)
|
23
21
|
else
|
24
22
|
nil
|
25
23
|
end
|
24
|
+
set_header k, v
|
26
25
|
end
|
27
26
|
end
|
28
27
|
|
@@ -30,63 +29,75 @@ module ActionDispatch
|
|
30
29
|
content_mime_type && content_mime_type.to_s
|
31
30
|
end
|
32
31
|
|
32
|
+
def has_content_type?
|
33
|
+
has_header? 'CONTENT_TYPE'
|
34
|
+
end
|
35
|
+
|
33
36
|
# Returns the accepted MIME type for the request.
|
34
37
|
def accepts
|
35
|
-
|
36
|
-
header =
|
38
|
+
fetch_header("action_dispatch.request.accepts") do |k|
|
39
|
+
header = get_header('HTTP_ACCEPT').to_s.strip
|
37
40
|
|
38
|
-
if header.empty?
|
41
|
+
v = if header.empty?
|
39
42
|
[content_mime_type]
|
40
43
|
else
|
41
44
|
Mime::Type.parse(header)
|
42
45
|
end
|
46
|
+
set_header k, v
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
46
50
|
# Returns the MIME type for the \format used in the request.
|
47
51
|
#
|
48
|
-
# GET /posts/5.xml | request.format => Mime
|
49
|
-
# GET /posts/5.xhtml | request.format => Mime
|
50
|
-
# GET /posts/5 | request.format => Mime
|
52
|
+
# GET /posts/5.xml | request.format => Mime[:xml]
|
53
|
+
# GET /posts/5.xhtml | request.format => Mime[:html]
|
54
|
+
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
|
51
55
|
#
|
52
56
|
def format(view_path = [])
|
53
57
|
formats.first || Mime::NullType.instance
|
54
58
|
end
|
55
59
|
|
56
60
|
def formats
|
57
|
-
|
61
|
+
fetch_header("action_dispatch.request.formats") do |k|
|
58
62
|
params_readable = begin
|
59
63
|
parameters[:format]
|
60
64
|
rescue ActionController::BadRequest
|
61
65
|
false
|
62
66
|
end
|
63
67
|
|
64
|
-
if params_readable
|
68
|
+
v = if params_readable
|
65
69
|
Array(Mime[parameters[:format]])
|
66
70
|
elsif use_accept_header && valid_accept_header
|
67
71
|
accepts
|
72
|
+
elsif extension_format = format_from_path_extension
|
73
|
+
[extension_format]
|
68
74
|
elsif xhr?
|
69
|
-
[Mime
|
75
|
+
[Mime[:js]]
|
70
76
|
else
|
71
|
-
[Mime
|
77
|
+
[Mime[:html]]
|
72
78
|
end
|
79
|
+
set_header k, v
|
73
80
|
end
|
74
81
|
end
|
75
82
|
|
76
83
|
# Sets the \variant for template.
|
77
84
|
def variant=(variant)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
@variant = variant
|
85
|
+
variant = Array(variant)
|
86
|
+
|
87
|
+
if variant.all? { |v| v.is_a?(Symbol) }
|
88
|
+
@variant = ActiveSupport::ArrayInquirer.new(variant)
|
82
89
|
else
|
83
|
-
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols
|
90
|
+
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \
|
84
91
|
"For security reasons, never directly set the variant to a user-provided value, " \
|
85
92
|
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
|
86
93
|
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
|
87
94
|
end
|
88
95
|
end
|
89
96
|
|
97
|
+
def variant
|
98
|
+
@variant ||= ActiveSupport::ArrayInquirer.new
|
99
|
+
end
|
100
|
+
|
90
101
|
# Sets the \format by string extension, which can be used to force custom formats
|
91
102
|
# that are not controlled by the extension.
|
92
103
|
#
|
@@ -100,7 +111,7 @@ module ActionDispatch
|
|
100
111
|
# end
|
101
112
|
def format=(extension)
|
102
113
|
parameters[:format] = extension.to_s
|
103
|
-
|
114
|
+
set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
|
104
115
|
end
|
105
116
|
|
106
117
|
# Sets the \formats by string extensions. This differs from #format= by allowing you
|
@@ -119,9 +130,9 @@ module ActionDispatch
|
|
119
130
|
# end
|
120
131
|
def formats=(extensions)
|
121
132
|
parameters[:format] = extensions.first.to_s
|
122
|
-
|
133
|
+
set_header "action_dispatch.request.formats", extensions.collect { |extension|
|
123
134
|
Mime::Type.lookup_by_extension(extension)
|
124
|
-
|
135
|
+
}
|
125
136
|
end
|
126
137
|
|
127
138
|
# Receives an array of mimes and return the first user sent mime that
|
@@ -151,6 +162,13 @@ module ActionDispatch
|
|
151
162
|
def use_accept_header
|
152
163
|
!self.class.ignore_accept_header
|
153
164
|
end
|
165
|
+
|
166
|
+
def format_from_path_extension
|
167
|
+
path = get_header('action_dispatch.original_path') || get_header('PATH_INFO')
|
168
|
+
if match = path && path.match(/\.(\w+)\z/)
|
169
|
+
Mime[match.captures.first]
|
170
|
+
end
|
171
|
+
end
|
154
172
|
end
|
155
173
|
end
|
156
174
|
end
|