root/trunk/actionpack/lib/action_controller/session/cookie_store.rb
| Revision 6184, 3.9 kB (checked in by bitsweat, 4 years ago) |
|---|
| Line | |
|---|---|
| 1 | require 'cgi' |
| 2 | require 'cgi/session' |
| 3 | require 'base64' # to make marshaled data HTTP header friendly |
| 4 | require 'digest/sha2' # to generate the data integrity hash |
| 5 | |
| 6 | # This cookie-based session store is the Rails default. Sessions typically |
| 7 | # contain at most a user_id and flash message; both fit within the 4K cookie |
| 8 | # size limit. Cookie-based sessions are dramatically faster than the |
| 9 | # alternatives. |
| 10 | # |
| 11 | # A secure hash is included with the cookie to ensure data integrity: |
| 12 | # a user cannot alter his user_id without knowing the secret key included in |
| 13 | # the hash. New apps are generated with a session :secret option in |
| 14 | # Application Controller. Set your own for old apps you're upgrading. |
| 15 | # Note that changing the secret invalidates all existing sessions! |
| 16 | # |
| 17 | # If you have more than 4K of session data or don't want your data to be |
| 18 | # visible to the user, pick another session store. |
| 19 | # |
| 20 | # CookieOverflow is raised if you attempt to store more than 4K of data. |
| 21 | # TamperedWithCookie is raised if the data integrity check fails. |
| 22 | class CGI::Session::CookieStore |
| 23 | # Cookies can typically store 4096 bytes. |
| 24 | MAX = 4096 |
| 25 | |
| 26 | # Raised when storing more than 4K of session data. |
| 27 | class CookieOverflow < StandardError; end |
| 28 | |
| 29 | # Raised when the cookie fails its integrity check. |
| 30 | class TamperedWithCookie < StandardError; end |
| 31 | |
| 32 | # Called from CGI::Session only. |
| 33 | def initialize(session, options = {}) |
| 34 | # The secret option is required. |
| 35 | if options['secret'].blank? |
| 36 | raise ArgumentError, 'A secret is required to generate an integrity hash for cookie session data. Use session :secret => "some secret phrase" in ApplicationController.' |
| 37 | end |
| 38 | |
| 39 | # Keep the session and its secret on hand so we can read and write cookies. |
| 40 | @session, @secret = session, options['secret'] |
| 41 | |
| 42 | # Default cookie options derived from session settings. |
| 43 | @cookie_options = { |
| 44 | 'name' => options['session_key'], |
| 45 | 'path' => options['session_path'], |
| 46 | 'domain' => options['session_domain'], |
| 47 | 'expires' => options['session_expires'], |
| 48 | 'secure' => options['session_secure'] |
| 49 | } |
| 50 | |
| 51 | # Set no_hidden and no_cookies since the session id is unused and we |
| 52 | # set our own data cookie. |
| 53 | options['no_hidden'] = true |
| 54 | options['no_cookies'] = true |
| 55 | end |
| 56 | |
| 57 | # Restore session data from the cookie. |
| 58 | def restore |
| 59 | @original = read_cookie |
| 60 | @data = unmarshal(@original) || {} |
| 61 | end |
| 62 | |
| 63 | # Wait until close to write the session data cookie. |
| 64 | def update; end |
| 65 | |
| 66 | # Write the session data cookie if it was loaded and has changed. |
| 67 | def close |
| 68 | if defined? @data |
| 69 | updated = marshal(@data) |
| 70 | raise CookieOverflow if updated.size > MAX |
| 71 | write_cookie('value' => updated) unless updated == @original |
| 72 | end |
| 73 | end |
| 74 | |
| 75 | # Delete the session data by setting an expired cookie with no data. |
| 76 | def delete |
| 77 | write_cookie('value' => '', 'expires' => 1.year.ago) |
| 78 | end |
| 79 | |
| 80 | private |
| 81 | # Marshal a session hash into safe cookie data. Include an integrity hash. |
| 82 | def marshal(session) |
| 83 | data = Base64.encode64(Marshal.dump(session)).chop |
| 84 | "#{data}--#{generate_digest(data)}" |
| 85 | end |
| 86 | |
| 87 | # Unmarshal cookie data to a hash and verify its integrity. |
| 88 | def unmarshal(cookie) |
| 89 | if cookie |
| 90 | data, digest = cookie.split('--') |
| 91 | raise TamperedWithCookie unless digest == generate_digest(data) |
| 92 | Marshal.load(Base64.decode64(data)) |
| 93 | end |
| 94 | end |
| 95 | |
| 96 | # Generate the inline SHA512 message digest. Larger (128 bytes) than SHA256 |
| 97 | # (64 bytes) or RMD160 (40 bytes), but small relative to the 4096 byte |
| 98 | # max cookie size. |
| 99 | def generate_digest(data) |
| 100 | Digest::SHA512.hexdigest "#{data}#{@secret}" |
| 101 | end |
| 102 | |
| 103 | # Read the session data cookie. |
| 104 | def read_cookie |
| 105 | @session.cgi.cookies[@cookie_options['name']].first |
| 106 | end |
| 107 | |
| 108 | # CGI likes to make you hack. |
| 109 | def write_cookie(options) |
| 110 | cookie = CGI::Cookie.new(@cookie_options.merge(options)) |
| 111 | @session.cgi.send :instance_variable_set, '@output_cookies', [cookie] |
| 112 | end |
| 113 | end |
Note: See TracBrowser for help on using the browser.