mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	 f845a9ef76
			
		
	
	
		f845a9ef76
		
	
	
	
	
		
			
			Follow r56795. Since Ruby 2.2, calling #close on a closed socket no longer raises exceptions. * lib/cgi/session.rb (update): remove closed? check * lib/net/http.rb (finish, transport_request): ditto * lib/net/imap.rb (disconnect): ditto * lib/net/pop.rb (do_start, do_finish): ditto * lib/net/smtp.rb (do_start, do_finish): ditto * lib/open3.rb (popen_run, pipeline_run): ditto * lib/pstore.rb (transaction): ditto * lib/shell/process-controller.rb (sfork): * lib/tempfile (_close, call, Tempfile.create): ditto * lib/webrick/httpauth/htdigest.rb (flush): ditto * lib/webrick/httpauth/htpasswd.rb (flush): ditto * lib/webrick/server.rb (start_thread, cleanup_shutdown_pipe): ditto git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@56865 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			534 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			534 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # frozen_string_literal: false
 | |
| #
 | |
| # cgi/session.rb - session support for cgi scripts
 | |
| #
 | |
| # Copyright (C) 2001  Yukihiro "Matz" Matsumoto
 | |
| # Copyright (C) 2000  Network Applied Communication Laboratory, Inc.
 | |
| # Copyright (C) 2000  Information-technology Promotion Agency, Japan
 | |
| #
 | |
| # Author: Yukihiro "Matz" Matsumoto
 | |
| #
 | |
| # Documentation: William Webber (william@williamwebber.com)
 | |
| 
 | |
| require 'cgi'
 | |
| require 'tmpdir'
 | |
| 
 | |
| class CGI
 | |
| 
 | |
|   # == Overview
 | |
|   #
 | |
|   # This file provides the CGI::Session class, which provides session
 | |
|   # support for CGI scripts.  A session is a sequence of HTTP requests
 | |
|   # and responses linked together and associated with a single client.
 | |
|   # Information associated with the session is stored
 | |
|   # on the server between requests.  A session id is passed between client
 | |
|   # and server with every request and response, transparently
 | |
|   # to the user.  This adds state information to the otherwise stateless
 | |
|   # HTTP request/response protocol.
 | |
|   #
 | |
|   # == Lifecycle
 | |
|   #
 | |
|   # A CGI::Session instance is created from a CGI object.  By default,
 | |
|   # this CGI::Session instance will start a new session if none currently
 | |
|   # exists, or continue the current session for this client if one does
 | |
|   # exist.  The +new_session+ option can be used to either always or
 | |
|   # never create a new session.  See #new() for more details.
 | |
|   #
 | |
|   # #delete() deletes a session from session storage.  It
 | |
|   # does not however remove the session id from the client.  If the client
 | |
|   # makes another request with the same id, the effect will be to start
 | |
|   # a new session with the old session's id.
 | |
|   #
 | |
|   # == Setting and retrieving session data.
 | |
|   #
 | |
|   # The Session class associates data with a session as key-value pairs.
 | |
|   # This data can be set and retrieved by indexing the Session instance
 | |
|   # using '[]', much the same as hashes (although other hash methods
 | |
|   # are not supported).
 | |
|   #
 | |
|   # When session processing has been completed for a request, the
 | |
|   # session should be closed using the close() method.  This will
 | |
|   # store the session's state to persistent storage.  If you want
 | |
|   # to store the session's state to persistent storage without
 | |
|   # finishing session processing for this request, call the update()
 | |
|   # method.
 | |
|   #
 | |
|   # == Storing session state
 | |
|   #
 | |
|   # The caller can specify what form of storage to use for the session's
 | |
|   # data with the +database_manager+ option to CGI::Session::new.  The
 | |
|   # following storage classes are provided as part of the standard library:
 | |
|   #
 | |
|   # CGI::Session::FileStore:: stores data as plain text in a flat file.  Only
 | |
|   #                           works with String data.  This is the default
 | |
|   #                           storage type.
 | |
|   # CGI::Session::MemoryStore:: stores data in an in-memory hash.  The data
 | |
|   #                             only persists for as long as the current Ruby
 | |
|   #                             interpreter instance does.
 | |
|   # CGI::Session::PStore:: stores data in Marshalled format.  Provided by
 | |
|   #                        cgi/session/pstore.rb.  Supports data of any type,
 | |
|   #                        and provides file-locking and transaction support.
 | |
|   #
 | |
|   # Custom storage types can also be created by defining a class with
 | |
|   # the following methods:
 | |
|   #
 | |
|   #    new(session, options)
 | |
|   #    restore  # returns hash of session data.
 | |
|   #    update
 | |
|   #    close
 | |
|   #    delete
 | |
|   #
 | |
|   # Changing storage type mid-session does not work.  Note in particular
 | |
|   # that by default the FileStore and PStore session data files have the
 | |
|   # same name.  If your application switches from one to the other without
 | |
|   # making sure that filenames will be different
 | |
|   # and clients still have old sessions lying around in cookies, then
 | |
|   # things will break nastily!
 | |
|   #
 | |
|   # == Maintaining the session id.
 | |
|   #
 | |
|   # Most session state is maintained on the server.  However, a session
 | |
|   # id must be passed backwards and forwards between client and server
 | |
|   # to maintain a reference to this session state.
 | |
|   #
 | |
|   # The simplest way to do this is via cookies.  The CGI::Session class
 | |
|   # provides transparent support for session id communication via cookies
 | |
|   # if the client has cookies enabled.
 | |
|   #
 | |
|   # If the client has cookies disabled, the session id must be included
 | |
|   # as a parameter of all requests sent by the client to the server.  The
 | |
|   # CGI::Session class in conjunction with the CGI class will transparently
 | |
|   # add the session id as a hidden input field to all forms generated
 | |
|   # using the CGI#form() HTML generation method.  No built-in support is
 | |
|   # provided for other mechanisms, such as URL re-writing.  The caller is
 | |
|   # responsible for extracting the session id from the session_id
 | |
|   # attribute and manually encoding it in URLs and adding it as a hidden
 | |
|   # input to HTML forms created by other mechanisms.  Also, session expiry
 | |
|   # is not automatically handled.
 | |
|   #
 | |
|   # == Examples of use
 | |
|   #
 | |
|   # === Setting the user's name
 | |
|   #
 | |
|   #   require 'cgi'
 | |
|   #   require 'cgi/session'
 | |
|   #   require 'cgi/session/pstore'     # provides CGI::Session::PStore
 | |
|   #
 | |
|   #   cgi = CGI.new("html4")
 | |
|   #
 | |
|   #   session = CGI::Session.new(cgi,
 | |
|   #       'database_manager' => CGI::Session::PStore,  # use PStore
 | |
|   #       'session_key' => '_rb_sess_id',              # custom session key
 | |
|   #       'session_expires' => Time.now + 30 * 60,     # 30 minute timeout
 | |
|   #       'prefix' => 'pstore_sid_')                   # PStore option
 | |
|   #   if cgi.has_key?('user_name') and cgi['user_name'] != ''
 | |
|   #       # coerce to String: cgi[] returns the
 | |
|   #       # string-like CGI::QueryExtension::Value
 | |
|   #       session['user_name'] = cgi['user_name'].to_s
 | |
|   #   elsif !session['user_name']
 | |
|   #       session['user_name'] = "guest"
 | |
|   #   end
 | |
|   #   session.close
 | |
|   #
 | |
|   # === Creating a new session safely
 | |
|   #
 | |
|   #   require 'cgi'
 | |
|   #   require 'cgi/session'
 | |
|   #
 | |
|   #   cgi = CGI.new("html4")
 | |
|   #
 | |
|   #   # We make sure to delete an old session if one exists,
 | |
|   #   # not just to free resources, but to prevent the session
 | |
|   #   # from being maliciously hijacked later on.
 | |
|   #   begin
 | |
|   #       session = CGI::Session.new(cgi, 'new_session' => false)
 | |
|   #       session.delete
 | |
|   #   rescue ArgumentError  # if no old session
 | |
|   #   end
 | |
|   #   session = CGI::Session.new(cgi, 'new_session' => true)
 | |
|   #   session.close
 | |
|   #
 | |
|   class Session
 | |
| 
 | |
|     class NoSession < RuntimeError #:nodoc:
 | |
|     end
 | |
| 
 | |
|     # The id of this session.
 | |
|     attr_reader :session_id, :new_session
 | |
| 
 | |
|     def Session::callback(dbman)  #:nodoc:
 | |
|       Proc.new{
 | |
|         dbman[0].close unless dbman.empty?
 | |
|       }
 | |
|     end
 | |
| 
 | |
|     # Create a new session id.
 | |
|     #
 | |
|     # The session id is a secure random number by SecureRandom
 | |
|     # if possible, otherwise an SHA512 hash based upon the time,
 | |
|     # a random number, and a constant string.  This routine is
 | |
|     # used internally for automatically generated session ids.
 | |
|     def create_new_id
 | |
|       require 'securerandom'
 | |
|       begin
 | |
|         # by OpenSSL, or system provided entropy pool
 | |
|         session_id = SecureRandom.hex(16)
 | |
|       rescue NotImplementedError
 | |
|         # never happens on modern systems
 | |
|         require 'digest'
 | |
|         d = Digest('SHA512').new
 | |
|         now = Time::now
 | |
|         d.update(now.to_s)
 | |
|         d.update(String(now.usec))
 | |
|         d.update(String(rand(0)))
 | |
|         d.update(String($$))
 | |
|         d.update('foobar')
 | |
|         session_id = d.hexdigest[0, 32]
 | |
|       end
 | |
|       session_id
 | |
|     end
 | |
|     private :create_new_id
 | |
| 
 | |
|     # Create a new CGI::Session object for +request+.
 | |
|     #
 | |
|     # +request+ is an instance of the +CGI+ class (see cgi.rb).
 | |
|     # +option+ is a hash of options for initialising this
 | |
|     # CGI::Session instance.  The following options are
 | |
|     # recognised:
 | |
|     #
 | |
|     # session_key:: the parameter name used for the session id.
 | |
|     #               Defaults to '_session_id'.
 | |
|     # session_id:: the session id to use.  If not provided, then
 | |
|     #              it is retrieved from the +session_key+ parameter
 | |
|     #              of the request, or automatically generated for
 | |
|     #              a new session.
 | |
|     # new_session:: if true, force creation of a new session.  If not set,
 | |
|     #               a new session is only created if none currently
 | |
|     #               exists.  If false, a new session is never created,
 | |
|     #               and if none currently exists and the +session_id+
 | |
|     #               option is not set, an ArgumentError is raised.
 | |
|     # database_manager:: the name of the class providing storage facilities
 | |
|     #                    for session state persistence.  Built-in support
 | |
|     #                    is provided for +FileStore+ (the default),
 | |
|     #                    +MemoryStore+, and +PStore+ (from
 | |
|     #                    cgi/session/pstore.rb).  See the documentation for
 | |
|     #                    these classes for more details.
 | |
|     #
 | |
|     # The following options are also recognised, but only apply if the
 | |
|     # session id is stored in a cookie.
 | |
|     #
 | |
|     # session_expires:: the time the current session expires, as a
 | |
|     #                   +Time+ object.  If not set, the session will terminate
 | |
|     #                   when the user's browser is closed.
 | |
|     # session_domain:: the hostname domain for which this session is valid.
 | |
|     #                  If not set, defaults to the hostname of the server.
 | |
|     # session_secure:: if +true+, this session will only work over HTTPS.
 | |
|     # session_path:: the path for which this session applies.  Defaults
 | |
|     #                to the directory of the CGI script.
 | |
|     #
 | |
|     # +option+ is also passed on to the session storage class initializer; see
 | |
|     # the documentation for each session storage class for the options
 | |
|     # they support.
 | |
|     #
 | |
|     # The retrieved or created session is automatically added to +request+
 | |
|     # as a cookie, and also to its +output_hidden+ table, which is used
 | |
|     # to add hidden input elements to forms.
 | |
|     #
 | |
|     # *WARNING* the +output_hidden+
 | |
|     # fields are surrounded by a <fieldset> tag in HTML 4 generation, which
 | |
|     # is _not_ invisible on many browsers; you may wish to disable the
 | |
|     # use of fieldsets with code similar to the following
 | |
|     # (see http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/37805)
 | |
|     #
 | |
|     #   cgi = CGI.new("html4")
 | |
|     #   class << cgi
 | |
|     #       undef_method :fieldset
 | |
|     #   end
 | |
|     #
 | |
|     def initialize(request, option={})
 | |
|       @new_session = false
 | |
|       session_key = option['session_key'] || '_session_id'
 | |
|       session_id = option['session_id']
 | |
|       unless session_id
 | |
|         if option['new_session']
 | |
|           session_id = create_new_id
 | |
|           @new_session = true
 | |
|         end
 | |
|       end
 | |
|       unless session_id
 | |
|         if request.key?(session_key)
 | |
|           session_id = request[session_key]
 | |
|           session_id = session_id.read if session_id.respond_to?(:read)
 | |
|         end
 | |
|         unless session_id
 | |
|           session_id, = request.cookies[session_key]
 | |
|         end
 | |
|         unless session_id
 | |
|           unless option.fetch('new_session', true)
 | |
|             raise ArgumentError, "session_key `%s' should be supplied"%session_key
 | |
|           end
 | |
|           session_id = create_new_id
 | |
|           @new_session = true
 | |
|         end
 | |
|       end
 | |
|       @session_id = session_id
 | |
|       dbman = option['database_manager'] || FileStore
 | |
|       begin
 | |
|         @dbman = dbman::new(self, option)
 | |
|       rescue NoSession
 | |
|         unless option.fetch('new_session', true)
 | |
|           raise ArgumentError, "invalid session_id `%s'"%session_id
 | |
|         end
 | |
|         session_id = @session_id = create_new_id unless session_id
 | |
|         @new_session=true
 | |
|         retry
 | |
|       end
 | |
|       request.instance_eval do
 | |
|         @output_hidden = {session_key => session_id} unless option['no_hidden']
 | |
|         @output_cookies =  [
 | |
|           Cookie::new("name" => session_key,
 | |
|           "value" => session_id,
 | |
|           "expires" => option['session_expires'],
 | |
|           "domain" => option['session_domain'],
 | |
|           "secure" => option['session_secure'],
 | |
|           "path" =>
 | |
|           if option['session_path']
 | |
|             option['session_path']
 | |
|           elsif ENV["SCRIPT_NAME"]
 | |
|             File::dirname(ENV["SCRIPT_NAME"])
 | |
|           else
 | |
|           ""
 | |
|           end)
 | |
|         ] unless option['no_cookies']
 | |
|       end
 | |
|       @dbprot = [@dbman]
 | |
|       ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
 | |
|     end
 | |
| 
 | |
|     # Retrieve the session data for key +key+.
 | |
|     def [](key)
 | |
|       @data ||= @dbman.restore
 | |
|       @data[key]
 | |
|     end
 | |
| 
 | |
|     # Set the session data for key +key+.
 | |
|     def []=(key, val)
 | |
|       @write_lock ||= true
 | |
|       @data ||= @dbman.restore
 | |
|       @data[key] = val
 | |
|     end
 | |
| 
 | |
|     # Store session data on the server.  For some session storage types,
 | |
|     # this is a no-op.
 | |
|     def update
 | |
|       @dbman.update
 | |
|     end
 | |
| 
 | |
|     # Store session data on the server and close the session storage.
 | |
|     # For some session storage types, this is a no-op.
 | |
|     def close
 | |
|       @dbman.close
 | |
|       @dbprot.clear
 | |
|     end
 | |
| 
 | |
|     # Delete the session from storage.  Also closes the storage.
 | |
|     #
 | |
|     # Note that the session's data is _not_ automatically deleted
 | |
|     # upon the session expiring.
 | |
|     def delete
 | |
|       @dbman.delete
 | |
|       @dbprot.clear
 | |
|     end
 | |
| 
 | |
|     # File-based session storage class.
 | |
|     #
 | |
|     # Implements session storage as a flat file of 'key=value' values.
 | |
|     # This storage type only works directly with String values; the
 | |
|     # user is responsible for converting other types to Strings when
 | |
|     # storing and from Strings when retrieving.
 | |
|     class FileStore
 | |
|       # Create a new FileStore instance.
 | |
|       #
 | |
|       # This constructor is used internally by CGI::Session.  The
 | |
|       # user does not generally need to call it directly.
 | |
|       #
 | |
|       # +session+ is the session for which this instance is being
 | |
|       # created.  The session id must only contain alphanumeric
 | |
|       # characters; automatically generated session ids observe
 | |
|       # this requirement.
 | |
|       #
 | |
|       # +option+ is a hash of options for the initializer.  The
 | |
|       # following options are recognised:
 | |
|       #
 | |
|       # tmpdir:: the directory to use for storing the FileStore
 | |
|       #          file.  Defaults to Dir::tmpdir (generally "/tmp"
 | |
|       #          on Unix systems).
 | |
|       # prefix:: the prefix to add to the session id when generating
 | |
|       #          the filename for this session's FileStore file.
 | |
|       #          Defaults to "cgi_sid_".
 | |
|       # suffix:: the prefix to add to the session id when generating
 | |
|       #          the filename for this session's FileStore file.
 | |
|       #          Defaults to the empty string.
 | |
|       #
 | |
|       # This session's FileStore file will be created if it does
 | |
|       # not exist, or opened if it does.
 | |
|       def initialize(session, option={})
 | |
|         dir = option['tmpdir'] || Dir::tmpdir
 | |
|         prefix = option['prefix'] || 'cgi_sid_'
 | |
|         suffix = option['suffix'] || ''
 | |
|         id = session.session_id
 | |
|         require 'digest/md5'
 | |
|         md5 = Digest::MD5.hexdigest(id)[0,16]
 | |
|         @path = dir+"/"+prefix+md5+suffix
 | |
|         if File::exist? @path
 | |
|           @hash = nil
 | |
|         else
 | |
|           unless session.new_session
 | |
|             raise CGI::Session::NoSession, "uninitialized session"
 | |
|           end
 | |
|           @hash = {}
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Restore session state from the session's FileStore file.
 | |
|       #
 | |
|       # Returns the session state as a hash.
 | |
|       def restore
 | |
|         unless @hash
 | |
|           @hash = {}
 | |
|           begin
 | |
|             lockf = File.open(@path+".lock", "r")
 | |
|             lockf.flock File::LOCK_SH
 | |
|             f = File.open(@path, 'r')
 | |
|             for line in f
 | |
|               line.chomp!
 | |
|               k, v = line.split('=',2)
 | |
|               @hash[CGI::unescape(k)] = Marshal.restore(CGI::unescape(v))
 | |
|             end
 | |
|           ensure
 | |
|             f.close unless f.nil?
 | |
|             lockf.close if lockf
 | |
|           end
 | |
|         end
 | |
|         @hash
 | |
|       end
 | |
| 
 | |
|       # Save session state to the session's FileStore file.
 | |
|       def update
 | |
|         return unless @hash
 | |
|         begin
 | |
|           lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600)
 | |
|           lockf.flock File::LOCK_EX
 | |
|           f = File.open(@path+".new", File::CREAT|File::TRUNC|File::WRONLY, 0600)
 | |
|           for k,v in @hash
 | |
|             f.printf "%s=%s\n", CGI::escape(k), CGI::escape(String(Marshal.dump(v)))
 | |
|           end
 | |
|           f.close
 | |
|           File.rename @path+".new", @path
 | |
|         ensure
 | |
|           f.close if f
 | |
|           lockf.close if lockf
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Update and close the session's FileStore file.
 | |
|       def close
 | |
|         update
 | |
|       end
 | |
| 
 | |
|       # Close and delete the session's FileStore file.
 | |
|       def delete
 | |
|         File::unlink @path+".lock" rescue nil
 | |
|         File::unlink @path+".new" rescue nil
 | |
|         File::unlink @path rescue nil
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # In-memory session storage class.
 | |
|     #
 | |
|     # Implements session storage as a global in-memory hash.  Session
 | |
|     # data will only persist for as long as the Ruby interpreter
 | |
|     # instance does.
 | |
|     class MemoryStore
 | |
|       GLOBAL_HASH_TABLE = {} #:nodoc:
 | |
| 
 | |
|       # Create a new MemoryStore instance.
 | |
|       #
 | |
|       # +session+ is the session this instance is associated with.
 | |
|       # +option+ is a list of initialisation options.  None are
 | |
|       # currently recognized.
 | |
|       def initialize(session, option=nil)
 | |
|         @session_id = session.session_id
 | |
|         unless GLOBAL_HASH_TABLE.key?(@session_id)
 | |
|           unless session.new_session
 | |
|             raise CGI::Session::NoSession, "uninitialized session"
 | |
|           end
 | |
|           GLOBAL_HASH_TABLE[@session_id] = {}
 | |
|         end
 | |
|       end
 | |
| 
 | |
|       # Restore session state.
 | |
|       #
 | |
|       # Returns session data as a hash.
 | |
|       def restore
 | |
|         GLOBAL_HASH_TABLE[@session_id]
 | |
|       end
 | |
| 
 | |
|       # Update session state.
 | |
|       #
 | |
|       # A no-op.
 | |
|       def update
 | |
|         # don't need to update; hash is shared
 | |
|       end
 | |
| 
 | |
|       # Close session storage.
 | |
|       #
 | |
|       # A no-op.
 | |
|       def close
 | |
|         # don't need to close
 | |
|       end
 | |
| 
 | |
|       # Delete the session state.
 | |
|       def delete
 | |
|         GLOBAL_HASH_TABLE.delete(@session_id)
 | |
|       end
 | |
|     end
 | |
| 
 | |
|     # Dummy session storage class.
 | |
|     #
 | |
|     # Implements session storage place holder.  No actual storage
 | |
|     # will be done.
 | |
|     class NullStore
 | |
|       # Create a new NullStore instance.
 | |
|       #
 | |
|       # +session+ is the session this instance is associated with.
 | |
|       # +option+ is a list of initialisation options.  None are
 | |
|       # currently recognised.
 | |
|       def initialize(session, option=nil)
 | |
|       end
 | |
| 
 | |
|       # Restore (empty) session state.
 | |
|       def restore
 | |
|         {}
 | |
|       end
 | |
| 
 | |
|       # Update session state.
 | |
|       #
 | |
|       # A no-op.
 | |
|       def update
 | |
|       end
 | |
| 
 | |
|       # Close session storage.
 | |
|       #
 | |
|       # A no-op.
 | |
|       def close
 | |
|       end
 | |
| 
 | |
|       # Delete the session state.
 | |
|       #
 | |
|       # A no-op.
 | |
|       def delete
 | |
|       end
 | |
|     end
 | |
|   end
 | |
| end
 |