1 <?php
  2 /**
  3  * @package     Joomla.Platform
  4  * @subpackage  Session
  5  *
  6  * @copyright   Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
  7  * @license     GNU General Public License version 2 or later; see LICENSE
  8  */
  9 
 10 defined('JPATH_PLATFORM') or die;
 11 
 12 /**
 13  * Interface for managing HTTP sessions
 14  *
 15  * @since       3.5
 16  * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 17  */
 18 class JSessionHandlerNative implements JSessionHandlerInterface
 19 {
 20     /**
 21      * Has the session been started
 22      *
 23      * @var    boolean
 24      * @since  3.5
 25      */
 26     private $started = false;
 27 
 28     /**
 29      * Has the session been closed
 30      *
 31      * @var    boolean
 32      * @since  3.5
 33      */
 34     private $closed = false;
 35 
 36     /**
 37      * Starts the session
 38      *
 39      * @return  boolean  True if started
 40      *
 41      * @since   3.5
 42      */
 43     public function start()
 44     {
 45         if ($this->isStarted())
 46         {
 47             return true;
 48         }
 49 
 50         $this->doSessionStart();
 51 
 52         return true;
 53     }
 54 
 55     /**
 56      * Checks if the session is started.
 57      *
 58      * @return  boolean  True if started, false otherwise.
 59      *
 60      * @since   3.5
 61      */
 62     public function isStarted()
 63     {
 64         return $this->started;
 65     }
 66 
 67     /**
 68      * Returns the session ID
 69      *
 70      * @return  string  The session ID
 71      *
 72      * @since   3.5
 73      */
 74     public function getId()
 75     {
 76         return session_id();
 77     }
 78 
 79     /**
 80      * Sets the session ID
 81      *
 82      * @param   string  $id  The session ID
 83      *
 84      * @return  void
 85      *
 86      * @since   3.5
 87      * @throws  LogicException
 88      */
 89     public function setId($id)
 90     {
 91         if ($this->isStarted())
 92         {
 93             throw new LogicException('Cannot change the ID of an active session');
 94         }
 95 
 96         session_id($id);
 97     }
 98 
 99     /**
100      * Returns the session name
101      *
102      * @return  mixed  The session name
103      *
104      * @since   3.5
105      */
106     public function getName()
107     {
108         return session_name();
109     }
110 
111     /**
112      * Sets the session name
113      *
114      * @param   string  $name  The name of the session
115      *
116      * @return  void
117      *
118      * @since   3.5
119      * @throws  LogicException
120      */
121     public function setName($name)
122     {
123         if ($this->isStarted())
124         {
125             throw new LogicException('Cannot change the name of an active session');
126         }
127 
128         session_name($name);
129     }
130 
131     /**
132      * Regenerates ID that represents this storage.
133      *
134      * Note regenerate+destroy should not clear the session data in memory only delete the session data from persistent storage.
135      *
136      * @param   boolean  $destroy   Destroy session when regenerating?
137      * @param   integer  $lifetime  Sets the cookie lifetime for the session cookie. A null value will leave the system settings unchanged,
138      *                              0 sets the cookie to expire with browser session. Time is in seconds, and is not a Unix timestamp.
139      *
140      * @return  boolean  True if session regenerated, false if error
141      *
142      * @since   3.5
143      */
144     public function regenerate($destroy = false, $lifetime = null)
145     {
146         if (null !== $lifetime)
147         {
148             ini_set('session.cookie_lifetime', $lifetime);
149         }
150 
151         $return = session_regenerate_id($destroy);
152 
153         // Workaround for https://bugs.php.net/bug.php?id=61470 as suggested by David Grudl
154         session_write_close();
155         $this->closed = true;
156 
157         if (isset($_SESSION))
158         {
159             $backup = $_SESSION;
160             $this->doSessionStart();
161             $_SESSION = $backup;
162         }
163         else
164         {
165             $this->doSessionStart();
166         }
167 
168         return $return;
169     }
170 
171     /**
172      * Force the session to be saved and closed.
173      *
174      * This method must invoke session_write_close() unless this interface is used for a storage object design for unit or functional testing where
175      * a real PHP session would interfere with testing, in which case it should actually persist the session data if required.
176      *
177      * @return  void
178      *
179      * @see     session_write_close()
180      * @since   3.5
181      */
182     public function save()
183     {
184         // Verify if the session is active
185         if ((version_compare(PHP_VERSION, '5.4', 'ge') && PHP_SESSION_ACTIVE === session_status())
186             || (version_compare(PHP_VERSION, '5.4', 'lt') && $this->started && isset($_SESSION) && $this->getId()))
187         {
188             $session = JFactory::getSession();
189             $data    = $session->getData();
190 
191             // Before storing it, let's serialize and encode the Registry object
192             $_SESSION['joomla'] = base64_encode(serialize($data));
193 
194             session_write_close();
195 
196             $this->closed  = true;
197             $this->started = false;
198         }
199     }
200 
201     /**
202      * Clear all session data in memory.
203      *
204      * @return  void
205      *
206      * @since   3.5
207      */
208     public function clear()
209     {
210         // Need to destroy any existing sessions started with session.auto_start
211         if ($this->getId())
212         {
213             session_unset();
214             session_destroy();
215         }
216 
217         $this->closed  = true;
218         $this->started = false;
219     }
220 
221     /**
222      * Performs the session start mechanism
223      *
224      * @return  void
225      *
226      * @since   3.5.1
227      * @throws  RuntimeException If something goes wrong starting the session.
228      */
229     private function doSessionStart()
230     {
231         // Register our function as shutdown method, so we can manipulate it
232         register_shutdown_function(array($this, 'save'));
233 
234         // Disable the cache limiter
235         session_cache_limiter('none');
236 
237         /*
238          * Extended checks to determine if the session has already been started
239          */
240 
241         // If running PHP 5.4, try to use the native API
242         if (version_compare(PHP_VERSION, '5.4', 'ge') && PHP_SESSION_ACTIVE === session_status())
243         {
244             throw new RuntimeException('Failed to start the session: already started by PHP.');
245         }
246 
247         // Fallback check for PHP 5.3
248         if (version_compare(PHP_VERSION, '5.4', 'lt') && !$this->closed && isset($_SESSION) && $this->getId())
249         {
250             throw new RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).');
251         }
252 
253         // If we are using cookies (default true) and headers have already been started (early output),
254         if (ini_get('session.use_cookies') && headers_sent($file, $line))
255         {
256             throw new RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
257         }
258 
259         // Ok to try and start the session
260         if (!session_start())
261         {
262             throw new RuntimeException('Failed to start the session');
263         }
264 
265         // Mark ourselves as started
266         $this->started = true;
267     }
268 }
269