diff --git a/core/core.services.yml b/core/core.services.yml index afb710b..da569d0 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -708,6 +708,17 @@ services: factory_service: authentication arguments: ['@request'] synchronized: true + session_handler: + class: Drupal\Core\Session\SessionHandler + arguments: ['@request', '@database'] + #scope: request + #synchronized: true + # Since PHP 5, write and close handlers are called after destructing objects, + # so destructors can use sessions, but the session handler cannot use + # objects. Thus, terminate with the kernel, before objects are destructed. + tags: + - { name: persist } + - { name: needs_destruction } asset.css.collection_renderer: class: Drupal\Core\Asset\CssCollectionRenderer arguments: [ '@state' ] diff --git a/core/includes/session.inc b/core/includes/session.inc index 5d23cca..e721055 100644 --- a/core/includes/session.inc +++ b/core/includes/session.inc @@ -17,10 +17,9 @@ function drupal_session_initialize() { global $user; // Register the default session handler. - // The shutdown function is only conditionally registered when a session has - // actually been started. - // @see \Drupal\Core\Session\SessionHandler::read() - $handler = new SessionHandler(\Drupal::request(), \Drupal::database()); + // The session handler manually shuts down with the kernel. + // @see \Drupal\Core\Session\SessionHandler::destruct() + $handler = \Drupal::service('session_handler'); session_set_save_handler($handler, FALSE); $is_https = \Drupal::request()->isSecure(); diff --git a/core/lib/Drupal/Core/Authentication/Provider/Cookie.php b/core/lib/Drupal/Core/Authentication/Provider/Cookie.php index ae108dc..7c6c5fb 100644 --- a/core/lib/Drupal/Core/Authentication/Provider/Cookie.php +++ b/core/lib/Drupal/Core/Authentication/Provider/Cookie.php @@ -44,7 +44,7 @@ public function authenticate(Request $request) { * {@inheritdoc} */ public function cleanup(Request $request) { - drupal_session_commit(); + drupal_session_commit(); # } /** diff --git a/core/lib/Drupal/Core/Session/SessionHandler.php b/core/lib/Drupal/Core/Session/SessionHandler.php index bf738e3..369c431 100644 --- a/core/lib/Drupal/Core/Session/SessionHandler.php +++ b/core/lib/Drupal/Core/Session/SessionHandler.php @@ -10,13 +10,14 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\Settings; use Drupal\Core\Database\Connection; +use Drupal\Core\DestructableInterface; use Drupal\Core\Utility\Error; use Symfony\Component\HttpFoundation\Request; /** * Default session handler. */ -class SessionHandler implements \SessionHandlerInterface { +class SessionHandler implements \SessionHandlerInterface, DestructableInterface { /** * The request. @@ -61,12 +62,6 @@ public function open($save_path, $name) { public function read($sid) { global $user; - // Write and Close handlers are called after destructing objects - // since PHP 5.0.5. - // Thus destructors can use sessions but session handler can't use objects. - // So we are moving session closure before destructing objects. - drupal_register_shutdown_function(array($this, 'close')); - // Handle the case of first time visitors and clients that don't store // cookies (eg. web crawlers). $insecure_session_name = substr(session_name(), 1); @@ -151,11 +146,9 @@ public function write($sid, $value) { // For performance reasons, do not update the sessions table, unless // $_SESSION has changed or more than 180 has passed since the last update. - $needs_update = FALSE; - if ($user) { - $needs_update = !$user->getLastAccessedTime() || REQUEST_TIME - $user->getLastAccessedTime() > Settings::getSingleton()->get('session_write_interval', 180); - } - if ($user && ($is_changed || $needs_update)) { + $needs_update = !$user->getLastAccessedTime() || REQUEST_TIME - $user->getLastAccessedTime() > Settings::getSingleton()->get('session_write_interval', 180); + + if ($is_changed || $needs_update) { // Either ssid or sid or both will be added from $key below. $fields = array( 'uid' => $user->id(), @@ -191,7 +184,7 @@ public function write($sid, $value) { ->execute(); } // Likewise, do not update access time more than once per 180 seconds. - if ($user && $user->isAuthenticated() && REQUEST_TIME - $user->getLastAccessedTime() > Settings::getSingleton()->get('session_write_interval', 180)) { + if ($user->isAuthenticated() && REQUEST_TIME - $user->getLastAccessedTime() > Settings::getSingleton()->get('session_write_interval', 180)) { $this->connection->update('users') ->fields(array( 'access' => REQUEST_TIME @@ -287,4 +280,11 @@ protected function deleteCookie($name, $secure = NULL) { } } + /** + * {@inheritdoc} + */ + public function destruct() { + session_write_close(); + } + } diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 9795455..b67b308 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -1098,10 +1098,12 @@ protected function curlInitialize() { if (!isset($this->curlHandle)) { $this->curlHandle = curl_init(); - // Some versions/configurations of cURL break on a NULL cookie jar, so - // supply a real file. + // CURLOPT_COOKIEJAR needs to be set for cURL to parse cookies and cURL + // will save cookies into this file upon curl_close(). + // The pathname must be absolute on Windows. + // @see http://php.net/manual/en/function.curl-setopt.php#75525 if (empty($this->cookieFile)) { - $this->cookieFile = $this->public_files_directory . '/cookie.jar'; + $this->cookieFile = DRUPAL_ROOT . '/' . $this->public_files_directory . '/cookie.jar'; } $curl_options = array( diff --git a/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php b/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php index 16d3eec..8e1823c 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Session/SessionTest.php @@ -20,6 +20,8 @@ class SessionTest extends WebTestBase { protected $dumpHeaders = TRUE; + protected $loggedInUsers = array(); + public static function getInfo() { return array( 'name' => 'Session tests', @@ -273,15 +275,32 @@ function testEmptySessionID() { */ function sessionReset($uid = 0) { // Close the internal browser. + // This causes cURL to write out the CURLOPT_COOKIEJAR file. $this->curlClose(); - $this->loggedInUser = FALSE; + + if (!empty($this->loggedInUser)) { + $this->loggedInUsers[$this->loggedInUser->id()] = $this->loggedInUser; + } + if ($uid) { + $this->loggedInUser = isset($this->loggedInUsers[$uid]) ? $this->loggedInUsers[$uid] : FALSE; + } + else { + $this->loggedInUser = FALSE; + } // Change cookie file for user. - $this->cookieFile = file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath() . '/cookie.' . $uid . '.txt'; + // $this->cookieFile is set as CURLOPT_COOKIEJAR, into which cURL will save + // cookies upon curl_close(). The pathname must be absolute on Windows. + // @see WebTestBase::curlInitialize() + // @see http://php.net/manual/en/function.curl-setopt.php#75525 + $this->cookieFile = DRUPAL_ROOT . '/' . $this->public_files_directory . '/cookie.' . $uid . '.jar'; + // The CURLOPT_COOKIEFILE option specifies a file from which cURL will read + // cookies to send with requests. By using the same file as for + // CURLOPT_COOKIEJAR, cURL will effectively read and write to the same file. $this->additionalCurlOptions[CURLOPT_COOKIEFILE] = $this->cookieFile; $this->additionalCurlOptions[CURLOPT_COOKIESESSION] = TRUE; $this->drupalGet('session-test/get'); - $this->assertResponse(200, 'Session test module is correctly enabled.', 'Session'); + $this->assertResponse(200); } /**