Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 354 lines (305 sloc) 11.982 kb
0a92704 Andrew White Ensure cookie keys are strings
pixeltrix authored
1 require 'active_support/core_ext/object/blank'
2 require 'active_support/core_ext/hash/keys'
db340da Xavier Noria adds a missing require from Active Support
fxn authored
3 require 'active_support/core_ext/module/attribute_accessors'
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
4
d2d4acf Joshua Peek Cookies middleware
josh authored
5 module ActionDispatch
6 class Request
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
7 def cookie_jar
8 env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
d2d4acf Joshua Peek Cookies middleware
josh authored
9 end
10 end
11
9f4d3a1 Joost Baaij expand cookie examples with signed and permanent methods
tilsammans authored
12 # \Cookies are read and written through ActionController#cookies.
d2d4acf Joshua Peek Cookies middleware
josh authored
13 #
14 # The cookies being read are the ones received along with the request, the cookies
15 # being written will be sent out with the response. Reading a cookie does not get
16 # the cookie object itself back, just the value it holds.
17 #
18 # Examples for writing:
19 #
20 # # Sets a simple session cookie.
5b9f6a7 Aditya Sanghi Resolving LH #5986, cookies doc updates
asanghi authored
21 # # This cookie will be deleted when the user's browser is closed.
d2d4acf Joshua Peek Cookies middleware
josh authored
22 # cookies[:user_name] = "david"
23 #
5b9f6a7 Aditya Sanghi Resolving LH #5986, cookies doc updates
asanghi authored
24 # # Assign an array of values to a cookie.
25 # cookies[:lat_lon] = [47.68, -122.37]
26 #
d2d4acf Joshua Peek Cookies middleware
josh authored
27 # # Sets a cookie that expires in 1 hour.
28 # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
29 #
9f4d3a1 Joost Baaij expand cookie examples with signed and permanent methods
tilsammans authored
30 # # Sets a signed cookie, which prevents a user from tampering with its value.
5b9f6a7 Aditya Sanghi Resolving LH #5986, cookies doc updates
asanghi authored
31 # # The cookie is signed by your app's <tt>config.secret_token</tt> value.
32 # # Rails generates this value by default when you create a new Rails app.
33 # cookies.signed[:user_id] = current_user.id
9f4d3a1 Joost Baaij expand cookie examples with signed and permanent methods
tilsammans authored
34 #
35 # # Sets a "permanent" cookie (which expires in 20 years from now).
36 # cookies.permanent[:login] = "XJ-122"
5b9f6a7 Aditya Sanghi Resolving LH #5986, cookies doc updates
asanghi authored
37 #
9f4d3a1 Joost Baaij expand cookie examples with signed and permanent methods
tilsammans authored
38 # # You can also chain these methods:
39 # cookies.permanent.signed[:login] = "XJ-122"
40 #
d2d4acf Joshua Peek Cookies middleware
josh authored
41 # Examples for reading:
42 #
43 # cookies[:user_name] # => "david"
44 # cookies.size # => 2
5b9f6a7 Aditya Sanghi Resolving LH #5986, cookies doc updates
asanghi authored
45 # cookies[:lat_lon] # => [47.68, -122.37]
d2d4acf Joshua Peek Cookies middleware
josh authored
46 #
47 # Example for deleting:
48 #
49 # cookies.delete :user_name
50 #
51 # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
52 #
53 # cookies[:key] = {
54 # :value => 'a yummy cookie',
55 # :expires => 1.year.from_now,
56 # :domain => 'domain.com'
57 # }
58 #
59 # cookies.delete(:key, :domain => 'domain.com')
60 #
61 # The option symbols for setting cookies are:
62 #
63 # * <tt>:value</tt> - The cookie's value or list of values (as an array).
fcdb5dc Sebastian Martinez Remove extra white spaces on ActionPack docs.
smartinez87 authored
64 # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
d2d4acf Joshua Peek Cookies middleware
josh authored
65 # of the application.
c09cd19 Carlos Antonio da Silva Small fix in cookie docs and trailing whitespaces
carlosantoniodasilva authored
66 # * <tt>:domain</tt> - The domain for which this cookie applies so you can
67 # restrict to the domain level. If you use a schema like www.example.com
f991326 Rizwan Reza Took out the domain option logic to cookies.rb.
rizwanreza authored
68 # and want to share session with user.example.com set <tt>:domain</tt>
c09cd19 Carlos Antonio da Silva Small fix in cookie docs and trailing whitespaces
carlosantoniodasilva authored
69 # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
6148b2d Rizwan Reza Adding missing docs to delete cookies with :all which were added that wa...
rizwanreza authored
70 # <tt>:all</tt> again when deleting keys.
f991326 Rizwan Reza Took out the domain option logic to cookies.rb.
rizwanreza authored
71 #
72 # :domain => nil # Does not sets cookie domain. (default)
73 # :domain => :all # Allow the cookie for the top most level
74 # domain and subdomains.
75 #
9f4d3a1 Joost Baaij expand cookie examples with signed and permanent methods
tilsammans authored
76 # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
d2d4acf Joshua Peek Cookies middleware
josh authored
77 # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
78 # Default is +false+.
79 # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
80 # only HTTP. Defaults to +false+.
81 class Cookies
d3e62fc José Valim Avoid creating a Rack::Response object in the cookie middleware since it...
josevalim authored
82 HTTP_HEADER = "Set-Cookie".freeze
83 TOKEN_KEY = "action_dispatch.secret_token".freeze
c09cd19 Carlos Antonio da Silva Small fix in cookie docs and trailing whitespaces
carlosantoniodasilva authored
84
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
85 # Raised when storing more than 4K of session data.
86 class CookieOverflow < StandardError; end
87
0ca69ca Aaron Patterson CookieJar should prefer composition over inheritance
tenderlove authored
88 class CookieJar #:nodoc:
fdd619e Aaron Patterson CookieJar is enumerable. fixes #2795
tenderlove authored
89 include Enumerable
f991326 Rizwan Reza Took out the domain option logic to cookies.rb.
rizwanreza authored
90
fd78bb7 Bryce Thornton Allow for any possible TLD when using the :all option with the cookie se...
brycethornton authored
91 # This regular expression is used to split the levels of a domain.
92 # The top level domain can be any string without a period or
93 # **.**, ***.** style TLDs like co.uk or com.au
94 #
95 # www.example.co.uk gives:
8491f16 Ravil Bayramgalin Add tld_length option when using domain :all in cookies
brainopia authored
96 # $& => example.co.uk
fd78bb7 Bryce Thornton Allow for any possible TLD when using the :all option with the cookie se...
brycethornton authored
97 #
98 # example.com gives:
8491f16 Ravil Bayramgalin Add tld_length option when using domain :all in cookies
brainopia authored
99 # $& => example.com
fd78bb7 Bryce Thornton Allow for any possible TLD when using the :all option with the cookie se...
brycethornton authored
100 #
101 # lots.of.subdomains.example.local gives:
8491f16 Ravil Bayramgalin Add tld_length option when using domain :all in cookies
brainopia authored
102 # $& => example.local
103 DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/
f991326 Rizwan Reza Took out the domain option logic to cookies.rb.
rizwanreza authored
104
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
105 def self.build(request)
d3e62fc José Valim Avoid creating a Rack::Response object in the cookie middleware since it...
josevalim authored
106 secret = request.env[TOKEN_KEY]
fd78bb7 Bryce Thornton Allow for any possible TLD when using the :all option with the cookie se...
brycethornton authored
107 host = request.host
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
108 secure = request.ssl?
b602ce6 Rizwan Reza Refactored duplication into a separate method. Dropped class variable.
rizwanreza authored
109
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
110 new(secret, host, secure).tap do |hash|
d2d4acf Joshua Peek Cookies middleware
josh authored
111 hash.update(request.cookies)
112 end
113 end
114
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
115 def initialize(secret = nil, host = nil, secure = false)
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
116 @secret = secret
d2d4acf Joshua Peek Cookies middleware
josh authored
117 @set_cookies = {}
118 @delete_cookies = {}
b602ce6 Rizwan Reza Refactored duplication into a separate method. Dropped class variable.
rizwanreza authored
119 @host = host
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
120 @secure = secure
0c5aded Santiago Pastorino raise if someone tries to modify the cookies when it was already streame...
spastorino authored
121 @closed = false
0ca69ca Aaron Patterson CookieJar should prefer composition over inheritance
tenderlove authored
122 @cookies = {}
d2d4acf Joshua Peek Cookies middleware
josh authored
123 end
124
fdd619e Aaron Patterson CookieJar is enumerable. fixes #2795
tenderlove authored
125 def each(&block)
126 @cookies.each(&block)
127 end
128
d2d4acf Joshua Peek Cookies middleware
josh authored
129 # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
130 def [](name)
0ca69ca Aaron Patterson CookieJar should prefer composition over inheritance
tenderlove authored
131 @cookies[name.to_s]
132 end
133
f34f0b7 José Valim Add has_key? and key? methods to CookieJar removed in 0ca69ca65f83b4bb34...
josevalim authored
134 def key?(name)
135 @cookies.key?(name.to_s)
136 end
137 alias :has_key? :key?
138
0ca69ca Aaron Patterson CookieJar should prefer composition over inheritance
tenderlove authored
139 def update(other_hash)
0a92704 Andrew White Ensure cookie keys are strings
pixeltrix authored
140 @cookies.update other_hash.stringify_keys
0ca69ca Aaron Patterson CookieJar should prefer composition over inheritance
tenderlove authored
141 self
d2d4acf Joshua Peek Cookies middleware
josh authored
142 end
143
b602ce6 Rizwan Reza Refactored duplication into a separate method. Dropped class variable.
rizwanreza authored
144 def handle_options(options) #:nodoc:
145 options[:path] ||= "/"
c09cd19 Carlos Antonio da Silva Small fix in cookie docs and trailing whitespaces
carlosantoniodasilva authored
146
b602ce6 Rizwan Reza Refactored duplication into a separate method. Dropped class variable.
rizwanreza authored
147 if options[:domain] == :all
8491f16 Ravil Bayramgalin Add tld_length option when using domain :all in cookies
brainopia authored
148 # if there is a provided tld length then we use it otherwise default domain regexp
149 domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP
150
ebc4746 Ravil Bayramgalin Fix edge cases for domain :all option on cookie store
brainopia authored
151 # if host is not ip and matches domain regexp
152 # (ip confirms to domain regexp so we explicitly check for ip)
8491f16 Ravil Bayramgalin Add tld_length option when using domain :all in cookies
brainopia authored
153 options[:domain] = if (@host !~ /^[\d.]+$/) && (@host =~ domain_regexp)
154 ".#{$&}"
ebc4746 Ravil Bayramgalin Fix edge cases for domain :all option on cookie store
brainopia authored
155 end
91a4193 Ravil Bayramgalin Support list of possible domains for cookies
brainopia authored
156 elsif options[:domain].is_a? Array
157 # if host matches one of the supplied domains without a dot in front of it
158 options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] }
b602ce6 Rizwan Reza Refactored duplication into a separate method. Dropped class variable.
rizwanreza authored
159 end
160 end
161
d2d4acf Joshua Peek Cookies middleware
josh authored
162 # Sets the cookie named +name+. The second argument may be the very cookie
163 # value, or a hash of options as documented above.
164 def []=(key, options)
165 if options.is_a?(Hash)
166 options.symbolize_keys!
167 value = options[:value]
168 else
169 value = options
170 options = { :value => value }
171 end
172
b602ce6 Rizwan Reza Refactored duplication into a separate method. Dropped class variable.
rizwanreza authored
173 handle_options(options)
c09cd19 Carlos Antonio da Silva Small fix in cookie docs and trailing whitespaces
carlosantoniodasilva authored
174
bbe634e Ravil Bayramgalin Dont stream back cookie value if it was set to the same value
brainopia authored
175 if @cookies[key.to_s] != value or options[:expires]
176 @cookies[key.to_s] = value
177 @set_cookies[key.to_s] = options
178 @delete_cookies.delete(key.to_s)
179 end
180
d2d4acf Joshua Peek Cookies middleware
josh authored
181 value
182 end
183
184 # Removes the cookie on the client machine by setting the value to an empty string
185 # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
186 # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
187 def delete(key, options = {})
188 options.symbolize_keys!
f991326 Rizwan Reza Took out the domain option logic to cookies.rb.
rizwanreza authored
189
b602ce6 Rizwan Reza Refactored duplication into a separate method. Dropped class variable.
rizwanreza authored
190 handle_options(options)
f991326 Rizwan Reza Took out the domain option logic to cookies.rb.
rizwanreza authored
191
0ca69ca Aaron Patterson CookieJar should prefer composition over inheritance
tenderlove authored
192 value = @cookies.delete(key.to_s)
7c000af Steven Bristol fixing sym and string cookie name, two cookies to browser bug.
stevenbristol authored
193 @delete_cookies[key.to_s] = options
d2d4acf Joshua Peek Cookies middleware
josh authored
194 value
195 end
196
d4658d8 Andrew White Refactor ActionController::TestCase cookies
pixeltrix authored
197 # Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
198 def clear(options = {})
199 @cookies.each_key{ |k| delete(k, options) }
200 end
201
d2d4acf Joshua Peek Cookies middleware
josh authored
202 # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example:
203 #
204 # cookies.permanent[:prefers_open_id] = true
205 # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
206 #
207 # This jar is only meant for writing. You'll read permanent cookies through the regular accessor.
208 #
209 # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
210 #
211 # cookies.permanent.signed[:remember_me] = current_user.id
c09cd19 Carlos Antonio da Silva Small fix in cookie docs and trailing whitespaces
carlosantoniodasilva authored
212 # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
d2d4acf Joshua Peek Cookies middleware
josh authored
213 def permanent
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
214 @permanent ||= PermanentCookieJar.new(self, @secret)
d2d4acf Joshua Peek Cookies middleware
josh authored
215 end
216
217 # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from
218 # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
219 # cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
220 # be raised.
221 #
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
222 # This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
d2d4acf Joshua Peek Cookies middleware
josh authored
223 #
224 # Example:
225 #
226 # cookies.signed[:discount] = 45
227 # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
228 #
229 # cookies.signed[:discount] # => 45
230 def signed
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
231 @signed ||= SignedCookieJar.new(self, @secret)
d2d4acf Joshua Peek Cookies middleware
josh authored
232 end
233
d3e62fc José Valim Avoid creating a Rack::Response object in the cookie middleware since it...
josevalim authored
234 def write(headers)
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
235 @set_cookies.each { |k, v| ::Rack::Utils.set_cookie_header!(headers, k, v) if write_cookie?(v) }
d3e62fc José Valim Avoid creating a Rack::Response object in the cookie middleware since it...
josevalim authored
236 @delete_cookies.each { |k, v| ::Rack::Utils.delete_cookie_header!(headers, k, v) }
d2d4acf Joshua Peek Cookies middleware
josh authored
237 end
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
238
e864ff7 Andrew White Add backward compatibility for testing cookies
pixeltrix authored
239 def recycle! #:nodoc:
d4658d8 Andrew White Refactor ActionController::TestCase cookies
pixeltrix authored
240 @set_cookies.clear
241 @delete_cookies.clear
242 end
243
98a1717 Sergey Nartimov configuration option to always write cookie
lest authored
244 mattr_accessor :always_write_cookie
245 self.always_write_cookie = false
246
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
247 private
248
249 def write_cookie?(cookie)
98a1717 Sergey Nartimov configuration option to always write cookie
lest authored
250 @secure || !cookie[:secure] || always_write_cookie
2d5a12a Andrew White Don't write out secure cookies unless the request is secure
pixeltrix authored
251 end
d2d4acf Joshua Peek Cookies middleware
josh authored
252 end
253
254 class PermanentCookieJar < CookieJar #:nodoc:
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
255 def initialize(parent_jar, secret)
256 @parent_jar, @secret = parent_jar, secret
d2d4acf Joshua Peek Cookies middleware
josh authored
257 end
258
259 def []=(key, options)
260 if options.is_a?(Hash)
261 options.symbolize_keys!
262 else
263 options = { :value => options }
264 end
265
266 options[:expires] = 20.years.from_now
267 @parent_jar[key] = options
268 end
269
270 def signed
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
271 @signed ||= SignedCookieJar.new(self, @secret)
d2d4acf Joshua Peek Cookies middleware
josh authored
272 end
273
274 def method_missing(method, *arguments, &block)
275 @parent_jar.send(method, *arguments, &block)
276 end
277 end
278
279 class SignedCookieJar < CookieJar #:nodoc:
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
280 MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes.
281 SECRET_MIN_LENGTH = 30 # Characters
282
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
283 def initialize(parent_jar, secret)
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
284 ensure_secret_secure(secret)
d2d4acf Joshua Peek Cookies middleware
josh authored
285 @parent_jar = parent_jar
6690d66 José Valim Rename config.cookie_secret to config.secret_token and pass it as config...
josevalim authored
286 @verifier = ActiveSupport::MessageVerifier.new(secret)
d2d4acf Joshua Peek Cookies middleware
josh authored
287 end
288
289 def [](name)
e395997 Jeremy Kemper Fix signed cookies by explicitly passing config to the cookie jar
jeremy authored
290 if signed_message = @parent_jar[name]
291 @verifier.verify(signed_message)
eeba755 Joshua Peek Accessing nonexistant cookies through the signed jar should not raise an
josh authored
292 end
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
293 rescue ActiveSupport::MessageVerifier::InvalidSignature
294 nil
d2d4acf Joshua Peek Cookies middleware
josh authored
295 end
296
297 def []=(key, options)
298 if options.is_a?(Hash)
299 options.symbolize_keys!
300 options[:value] = @verifier.generate(options[:value])
301 else
302 options = { :value => @verifier.generate(options) }
303 end
304
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
305 raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE
d2d4acf Joshua Peek Cookies middleware
josh authored
306 @parent_jar[key] = options
307 end
308
309 def method_missing(method, *arguments, &block)
310 @parent_jar.send(method, *arguments, &block)
311 end
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
312
313 protected
314
315 # To prevent users from using something insecure like "Password" we make sure that the
316 # secret they've provided is at least 30 characters in length.
317 def ensure_secret_secure(secret)
318 if secret.blank?
319 raise ArgumentError, "A secret is required to generate an " +
320 "integrity hash for cookie session data. Use " +
321 "config.secret_token = \"some secret phrase of at " +
322 "least #{SECRET_MIN_LENGTH} characters\"" +
5b9f6a7 Aditya Sanghi Resolving LH #5986, cookies doc updates
asanghi authored
323 "in config/initializers/secret_token.rb"
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
324 end
325
326 if secret.length < SECRET_MIN_LENGTH
327 raise ArgumentError, "Secret should be something secure, " +
05f29ca Sebastian Martinez Remove extra white-space on some exception messages.
smartinez87 authored
328 "like \"#{SecureRandom.hex(16)}\". The value you " +
25f7c03 José Valim Simplify cookie_store by simply relying on cookies.signed.
josevalim authored
329 "provided, \"#{secret}\", is shorter than the minimum length " +
330 "of #{SECRET_MIN_LENGTH} characters"
331 end
332 end
d2d4acf Joshua Peek Cookies middleware
josh authored
333 end
334
335 def initialize(app)
336 @app = app
337 end
338
339 def call(env)
0c5aded Santiago Pastorino raise if someone tries to modify the cookies when it was already streame...
spastorino authored
340 cookie_jar = nil
d2d4acf Joshua Peek Cookies middleware
josh authored
341 status, headers, body = @app.call(env)
342
343 if cookie_jar = env['action_dispatch.cookies']
d3e62fc José Valim Avoid creating a Rack::Response object in the cookie middleware since it...
josevalim authored
344 cookie_jar.write(headers)
345 if headers[HTTP_HEADER].respond_to?(:join)
346 headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n")
347 end
d2d4acf Joshua Peek Cookies middleware
josh authored
348 end
d3e62fc José Valim Avoid creating a Rack::Response object in the cookie middleware since it...
josevalim authored
349
350 [status, headers, body]
d2d4acf Joshua Peek Cookies middleware
josh authored
351 end
352 end
353 end
Something went wrong with that request. Please try again.