Page MenuHomeIn-Portal Phabricator

D559.id1449.diff
No OneTemporary

File Metadata

Created
Wed, Feb 25, 7:49 AM

D559.id1449.diff

Index: core/kernel/session/session.php
===================================================================
--- core/kernel/session/session.php
+++ core/kernel/session/session.php
@@ -95,7 +95,7 @@
var $CookieName = 'sid';
var $CookieDomain;
var $CookiePath;
- var $CookieSecure = 0;
+ protected $CookieSecure = false;
var $SessionTimeout = 3600;
var $Expiration;
@@ -370,38 +370,67 @@
}
/**
- * Sets cookie for current site using path and domain
+ * Sets cookie for the current site using path and domain.
*
- * @param string $name
- * @param mixed $value
- * @param int $expires
+ * @param string $name Name.
+ * @param mixed $value Value.
+ * @param integer|null $expires Expiration date.
+ * @param array $options Options.
+ *
+ * @return void
*/
- function SetCookie($name, $value, $expires = null)
+ public function SetCookie($name, $value, $expires = null, array $options = array())
{
- if (isset($expires) && $expires < adodb_mktime()) {
+ if ( $expires !== null ) {
+ $options['expires'] = $expires;
+ }
+
+ $default_options = array(
+ 'expires' => 0,
+ 'secure' => $this->CookieSecure,
+ 'httponly' => true,
+ );
+ $options += $default_options;
+
+ if ( $options['expires'] !== 0 && $options['expires'] < adodb_mktime() ) {
unset($this->Application->HttpQuery->Cookie[$name]);
}
else {
$this->Application->HttpQuery->Cookie[$name] = $value;
}
- $old_style_domains = Array (
- // domain like in pre 5.1.0 versions
+ $old_style_domains = array(
+ // Domain like in pre 5.1.0 versions.
'.' . SERVER_NAME,
- // auto-guessed domain (when user specified other domain in configuration variable)
- $this->_autoGuessDomain(SERVER_NAME)
+ // Auto-guessed domain (when the user specified another domain in the configuration variable).
+ $this->_autoGuessDomain(SERVER_NAME),
);
- foreach ($old_style_domains as $old_style_domain) {
- if ($this->CookieDomain != $old_style_domain) {
- // new style cookie domain -> delete old style cookie to prevent infinite redirect
- setcookie($name, $value, adodb_mktime() - 3600, $this->CookiePath, $old_style_domain, $this->CookieSecure, true);
-
+ foreach ( $old_style_domains as $old_style_domain ) {
+ if ( $this->CookieDomain != $old_style_domain ) {
+ // New style cookie domain -> delete old style cookie to prevent infinite redirect.
+ setcookie(
+ $name,
+ $value,
+ adodb_mktime() - 3600,
+ $this->CookiePath,
+ $old_style_domain,
+ $options['secure'],
+ $options['httponly']
+ );
}
}
- setcookie($name, $value, $expires, $this->CookiePath, $this->CookieDomain, $this->CookieSecure, true);
+ setcookie(
+ $name,
+ $value,
+ $options['expires'],
+ $this->CookiePath,
+ $this->CookieDomain,
+ $options['secure'],
+ $options['httponly']
+ );
}
function Check()
Index: core/tests/Unit/kernel/session/fixtures/cookies.php
===================================================================
--- /dev/null
+++ core/tests/Unit/kernel/session/fixtures/cookies.php
@@ -0,0 +1,38 @@
+<?php
+$start = microtime(true);
+
+define('DBG_SKIP_REPORTING', 1);
+define('FULL_PATH', realpath(__DIR__ . '/../../../../../..'));
+include_once(FULL_PATH . '/core/kernel/startup.php');
+
+$application =& kApplication::Instance();
+$application->Init();
+
+$future_date = strtotime('+1 day midnight');
+$past_date = strtotime('-1 hour midnight');
+
+switch ( $application->GetVar('mode') ) {
+ case 'defaults':
+ $application->Session->SetCookie('phpunit_session_only', 'v1');
+ $application->Session->SetCookie('phpunit_one_day', 'v1', $future_date);
+ $application->Session->SetCookie('phpunit_remove', 'v1', $past_date);
+ break;
+
+ case 'httponly-override':
+ $application->Session->SetCookie('phpunit_session_only', 'v1', null, array('httponly' => false));
+ $application->Session->SetCookie('phpunit_one_day', 'v1', $future_date, array('httponly' => false));
+ $application->Session->SetCookie('phpunit_remove', 'v1', $past_date, array('httponly' => false));
+ break;
+
+ case 'secure-override':
+ $application->Session->SetCookie('phpunit_session_only', 'v1', null, array('secure' => true));
+ $application->Session->SetCookie('phpunit_one_day', 'v1', $future_date, array('secure' => true));
+ $application->Session->SetCookie('phpunit_remove', 'v1', $past_date, array('secure' => true));
+ break;
+}
+
+echo 'Inspect the "Set-Cookie" headers in response.<br/>' . PHP_EOL;
+
+$application->Done();
+
+$end = microtime(true);
Index: core/tests/Unit/kernel/session/sessionTest.php
===================================================================
--- /dev/null
+++ core/tests/Unit/kernel/session/sessionTest.php
@@ -0,0 +1,158 @@
+<?php
+
+
+/**
+ * The class name must match the file name for Phabricator-invoked PHPUnit to run this test.
+ * TODO: Once "session.php" file is renamed we can rename this class/filename as well.
+ */
+class sessionTest extends AbstractTestCase // phpcs:ignore Squiz.Classes.ValidClassName.NotCamelCaps
+{
+
+ public function testSetCookieExpiration()
+ {
+ $cookie_headers = $this->parseSetCookieHeaders($this->getAllHeaders('defaults'));
+
+ $this->assertArrayNotHasKey(
+ 'expires',
+ $cookie_headers['phpunit_session_only'],
+ 'Session-only cookie has expires property.'
+ );
+ $this->assertGreaterThan(
+ time(),
+ strtotime($cookie_headers['phpunit_one_day']['expires']),
+ 'Cookie with expiration has expires property set to the past.'
+ );
+ $this->assertLessThan(
+ time(),
+ strtotime($cookie_headers['phpunit_remove']['expires']),
+ 'Deleted cookie has expires property set to the future.'
+ );
+ }
+
+ public function testSetCookieHttpOnlyDefaults()
+ {
+ $cookie_headers = $this->parseSetCookieHeaders($this->getAllHeaders('defaults'));
+
+ $this->assertNotEmpty(
+ $cookie_headers['phpunit_session_only']['httponly'],
+ 'Session-only cookie isn\'t httponly by default.'
+ );
+ $this->assertNotEmpty(
+ $cookie_headers['phpunit_one_day']['httponly'],
+ 'Cookie with expiration isn\'t httponly by default.'
+ );
+ $this->assertNotEmpty(
+ $cookie_headers['phpunit_remove']['httponly'],
+ 'Deleted cookie isn\'t httponly by default.'
+ );
+ }
+
+ public function testSetCookieHttpOnlyOverride()
+ {
+ $cookie_headers = $this->parseSetCookieHeaders($this->getAllHeaders('httponly-override'));
+
+ $this->assertArrayNotHasKey(
+ 'httponly',
+ $cookie_headers['phpunit_session_only'],
+ 'Session-only cookie httponly override failed.'
+ );
+ $this->assertArrayNotHasKey(
+ 'httponly',
+ $cookie_headers['phpunit_one_day'],
+ 'Cookie with expiration httponly override failed.'
+ );
+ $this->assertArrayNotHasKey(
+ 'httponly',
+ $cookie_headers['phpunit_remove'],
+ 'Deleted cookie httponly override failed.'
+ );
+ }
+
+ public function testSetCookieSecureDefaults()
+ {
+ $cookie_headers = $this->parseSetCookieHeaders($this->getAllHeaders('defaults'));
+
+ $this->assertArrayNotHasKey(
+ 'secure',
+ $cookie_headers['phpunit_session_only'],
+ 'Session-only cookie isn\'t unsecure by default.'
+ );
+ $this->assertArrayNotHasKey(
+ 'secure',
+ $cookie_headers['phpunit_one_day'],
+ 'Cookie with expiration isn\'t unsecure by default.'
+ );
+ $this->assertArrayNotHasKey(
+ 'secure',
+ $cookie_headers['phpunit_remove'],
+ 'Deleted cookie isn\'t unsecure by default.'
+ );
+ }
+
+ public function testSetCookieSecureOverride()
+ {
+ $cookie_headers = $this->parseSetCookieHeaders($this->getAllHeaders('secure-override'));
+
+ $this->assertNotEmpty(
+ $cookie_headers['phpunit_session_only']['secure'],
+ 'Session-only cookie secure override failed.'
+ );
+ $this->assertNotEmpty(
+ $cookie_headers['phpunit_one_day']['secure'],
+ 'Cookie with expiration secure override failed.'
+ );
+ $this->assertNotEmpty(
+ $cookie_headers['phpunit_remove']['secure'],
+ 'Deleted cookie secure override failed.'
+ );
+ }
+
+ protected function getAllHeaders($mode)
+ {
+ /** @var FileHelper $file_helper */
+ $file_helper = $this->Application->recallObject('FileHelper');
+ $script_url = $file_helper->pathToUrl(__DIR__ . '/fixtures/cookies.php');
+
+ /** @var kCurlHelper $curl_helper */
+ $curl_helper = $this->Application->recallObject('CurlHelper');
+ $curl_helper->Send($script_url . '?mode=' . $mode, false);
+ $response_headers = $curl_helper->getResponseHeaders();
+ $curl_helper->CloseConnection();
+
+ return $response_headers;
+ }
+
+ protected function parseSetCookieHeaders(array $headers)
+ {
+ $ret = array();
+
+ foreach ( $headers as $header ) {
+ if ( strpos($header, 'Set-Cookie: ') !== 0 ) {
+ continue;
+ }
+
+ $header = preg_replace('/^Set-Cookie:\s+/', '', $header);
+
+ $parsed_parts = array();
+ $raw_parts = array_map('trim', explode(';', $header));
+ list($cookie_name,) = explode('=', array_shift($raw_parts), 2);
+
+ foreach ( $raw_parts as $raw_part ) {
+ $raw_part_fragments = explode('=', $raw_part, 2);
+ $raw_fragment_name = strtolower($raw_part_fragments[0]);
+
+ if ( count($raw_part_fragments) === 2 ) {
+ $parsed_parts[$raw_fragment_name] = $raw_part_fragments[1];
+ }
+ else {
+ $parsed_parts[$raw_fragment_name] = true;
+ }
+ }
+
+ $ret[$cookie_name] = $parsed_parts;
+ }
+
+ return $ret;
+ }
+
+}

Event Timeline