ソースを参照

Use DateTime objects internally.

This will allow expiration dates to exist in the far future on 32bit
(windows) systems.
Mark Story 9 年 前
コミット
ed64d09845

+ 38 - 24
src/Http/Cookie/Cookie.php

@@ -16,6 +16,8 @@ namespace Cake\Http\Cookie;
 use Cake\Chronos\Chronos;
 use Cake\Utility\Hash;
 use DateTimeInterface;
+use DateTimeImmutable;
+use DateTimezone;
 use InvalidArgumentException;
 use RuntimeException;
 
@@ -79,9 +81,9 @@ class Cookie implements CookieInterface
     /**
      * Expiration time
      *
-     * @var int
+     * @var DateTimeInterface
      */
-    protected $expiresAt = 0;
+    protected $expiresAt;
 
     /**
      * Path
@@ -121,7 +123,7 @@ class Cookie implements CookieInterface
      * @link http://php.net/manual/en/function.setcookie.php
      * @param string $name Cookie name
      * @param string|array $value Value of the cookie
-     * @param \DateTimeInterface|int|null $expiresAt Expiration time and date
+     * @param \DateTimeInterface|null $expiresAt Expiration time and date
      * @param string $path Path
      * @param string $domain Domain
      * @param bool $secure Is secure
@@ -130,7 +132,7 @@ class Cookie implements CookieInterface
     public function __construct(
         $name,
         $value = '',
-        $expiresAt = null,
+        DateTimeInterface $expiresAt = null,
         $path = '',
         $domain = '',
         $secure = false,
@@ -152,13 +154,10 @@ class Cookie implements CookieInterface
 
         $this->validateBool($secure);
         $this->secure = $secure;
-
-        if (is_int($expiresAt)) {
-            $this->expiresAt = $expiresAt;
-        }
-        if ($expiresAt instanceof DateTimeInterface) {
-            $this->expiresAt = (int)$expiresAt->format('U');
+        if ($expiresAt) {
+            $expiresAt = $expiresAt->setTimezone(new DateTimezone('GMT'));
         }
+        $this->expiresAt = $expiresAt;
     }
 
     /**
@@ -166,12 +165,27 @@ class Cookie implements CookieInterface
      *
      * @return string
      */
-    protected function _buildExpirationValue()
+    protected function getFormattedExpires()
+    {
+        if (!$this->expiresAt) {
+            return '';
+        }
+
+        return $this->expiresAt->format(static::EXPIRES_FORMAT);
+    }
+
+    /**
+     * Get the timestamp from the expiration time
+     *
+     * @return int
+     */
+    public function getExpiresTimestamp()
     {
-        return sprintf(
-            'expires=%s',
-            gmdate(static::EXPIRES_FORMAT, $this->expiresAt)
-        );
+        if (!$this->expiresAt) {
+            return 0;
+        }
+
+        return (int)$this->expiresAt->format('U');
     }
 
     /**
@@ -187,8 +201,8 @@ class Cookie implements CookieInterface
         }
         $headerValue[] = sprintf('%s=%s', $this->name, urlencode($value));
 
-        if ($this->expiresAt !== 0) {
-            $headerValue[] = $this->_buildExpirationValue();
+        if ($this->expiresAt) {
+            $headerValue[] = sprintf('expires=%s', $this->getFormattedExpires());
         }
         if ($this->path !== '') {
             $headerValue[] = sprintf('path=%s', $this->path);
@@ -444,7 +458,7 @@ class Cookie implements CookieInterface
     public function withExpiry(DateTimeInterface $dateTime)
     {
         $new = clone $this;
-        $new->expiresAt = (int)$dateTime->format('U');
+        $new->expiresAt = $dateTime->setTimezone(new DateTimezone('GMT'));
 
         return $new;
     }
@@ -452,7 +466,7 @@ class Cookie implements CookieInterface
     /**
      * Get the current expiry time
      *
-     * @return int|null Timestamp of expiry or null
+     * @return DateTimeInterface|null Timestamp of expiry or null
      */
     public function getExpiry()
     {
@@ -467,7 +481,7 @@ class Cookie implements CookieInterface
     public function withNeverExpire()
     {
         $new = clone $this;
-        $new->expiresAt = Chronos::createFromDate(2038, 1, 1)->format('U');
+        $new->expiresAt = Chronos::createFromDate(2038, 1, 1);
 
         return $new;
     }
@@ -482,7 +496,7 @@ class Cookie implements CookieInterface
     public function withExpired()
     {
         $new = clone $this;
-        $new->expiresAt = Chronos::parse('-1 year')->format('U');
+        $new->expiresAt = Chronos::parse('-1 year');
 
         return $new;
     }
@@ -622,7 +636,7 @@ class Cookie implements CookieInterface
             'domain' => $this->getDomain(),
             'secure' => $this->isSecure(),
             'httponly' => $this->isHttpOnly(),
-            'expires' => $this->getExpiry()
+            'expires' => $this->getExpiresTimestamp()
         ];
     }
 
@@ -643,7 +657,7 @@ class Cookie implements CookieInterface
             'domain' => $this->getDomain(),
             'secure' => $this->isSecure(),
             'httponly' => $this->isHttpOnly(),
-            'expires' => gmdate(static::EXPIRES_FORMAT, $this->expiresAt)
+            'expires' => $this->getFormattedExpires()
         ];
     }
 
@@ -664,7 +678,7 @@ class Cookie implements CookieInterface
             'domain' => $this->getDomain(),
             'secure' => $this->isSecure(),
             'httpOnly' => $this->isHttpOnly(),
-            'expire' => $this->expiresAt
+            'expire' => $this->getExpiresTimestamp()
         ];
     }
 

+ 2 - 2
src/Http/Cookie/CookieCollection.php

@@ -253,7 +253,7 @@ class CookieCollection implements IteratorAggregate, Countable
                 $domain = ltrim($domain, '.');
             }
 
-            $expires = $cookie->getExpiry();
+            $expires = $cookie->getExpiresTimestamp();
             if ($expires && time() > $expires) {
                 continue;
             }
@@ -389,7 +389,7 @@ class CookieCollection implements IteratorAggregate, Countable
         $hostPattern = '/' . preg_quote($host, '/') . '$/';
 
         foreach ($this->cookies as $i => $cookie) {
-            $expires = $cookie->getExpiry();
+            $expires = $cookie->getExpiresTimestamp();
             $expired = ($expires > 0 && $expires < $time);
 
             $pathMatches = strpos($path, $cookie->getPath()) === 0;

+ 4 - 2
src/Http/Response.php

@@ -1951,10 +1951,11 @@ class Response implements ResponseInterface
             'secure' => false,
             'httpOnly' => false
         ];
+        $expires = $options['expire'] ? new DateTime('@' . $options['expire']) : null;
         $cookie = new Cookie(
             $options['name'],
             $options['value'],
-            (int)$options['expire'],
+            $expires,
             $options['path'],
             $options['domain'],
             $options['secure'],
@@ -2009,10 +2010,11 @@ class Response implements ResponseInterface
                 'secure' => false,
                 'httpOnly' => false
             ];
+            $expires = $data['expire'] ? new DateTime('@' . $data['expire']) : null;
             $cookie = new Cookie(
                 $name,
                 $data['value'],
-                (int)$data['expire'],
+                $expires,
                 $data['path'],
                 $data['domain'],
                 $data['secure'],

+ 3 - 3
tests/TestCase/Http/Cookie/CookieCollectionTest.php

@@ -222,14 +222,14 @@ class CookieCollectionTest extends TestCase
         $this->assertSame('/app', $new->get('test')->getPath(), 'cookies should inherit request path');
         $this->assertSame('/', $new->get('expiring')->getPath(), 'path attribute should be used.');
 
-        $this->assertSame(0, $new->get('test')->getExpiry(), 'No expiry');
+        $this->assertNull($new->get('test')->getExpiry(), 'No expiry');
         $this->assertSame(
             '2021-06-09 10:18:14',
-            date('Y-m-d H:i:s', $new->get('expiring')->getExpiry()),
+            $new->get('expiring')->getExpiry()->format('Y-m-d H:i:s'),
             'Has expiry'
         );
         $session = $new->get('session');
-        $this->assertSame(0, $session->getExpiry(), 'No expiry');
+        $this->assertNull($session->getExpiry(), 'No expiry');
         $this->assertSame('www.example.com', $session->getDomain(), 'Has domain');
     }
 

+ 20 - 1
tests/TestCase/Http/Cookie/CookieTest.php

@@ -356,6 +356,25 @@ class CookieTest extends TestCase
     }
 
     /**
+     * Test the withExpiry method changes timezone
+     *
+     * @return void
+     */
+    public function testWithExpiryChangesTimezone()
+    {
+        $cookie = new Cookie('cakephp', 'cakephp-rocks');
+        $date = Chronos::createFromDate(2022, 6, 15);
+        $date = $date->setTimezone('America/New_York');
+
+        $new = $cookie->withExpiry($date);
+        $this->assertNotSame($new, $cookie, 'Should clone');
+        $this->assertNotContains('expires', $cookie->toHeaderValue());
+
+        $this->assertContains('expires=Wed, 15-Jun-2022', $new->toHeaderValue());
+        $this->assertContains('GMT', $new->toHeaderValue());
+    }
+
+    /**
      * Test the withName method
      *
      * @return void
@@ -559,7 +578,7 @@ class CookieTest extends TestCase
             'value' => 'cakephp-rocks',
             'path' => '/api',
             'domain' => 'cakephp.org',
-            'expires' => (int)$date->format('U'),
+            'expires' => $date->format('U'),
             'secure' => true,
             'httponly' => true
         ];