diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 513182b8776b..b078a6cac5c7 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -35,6 +35,10 @@ class Filesystem */ public function copy($originFile, $targetFile, $override = false) { + if (!is_file($originFile)) { + throw new IOException(sprintf('Failed to copy %s because file not exists', $originFile)); + } + $this->mkdir(dirname($targetFile)); if (!$override && is_file($targetFile)) { @@ -44,7 +48,15 @@ public function copy($originFile, $targetFile, $override = false) } if ($doCopy) { - if (true !== @copy($originFile, $targetFile)) { + // https://bugs.php.net/bug.php?id=64634 + $source = fopen($originFile, 'r'); + $target = fopen($targetFile, 'w+'); + stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + unset($source, $target); + + if (!is_file($targetFile)) { throw new IOException(sprintf('Failed to copy %s to %s', $originFile, $targetFile)); } } diff --git a/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php b/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php index c5ec81051fb3..6384af9660a8 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php @@ -29,13 +29,12 @@ class EsiResponseCacheStrategy implements EsiResponseCacheStrategyInterface { private $cacheable = true; + private $embeddedResponses = 0; private $ttls = array(); private $maxAges = array(); /** - * Adds a Response. - * - * @param Response $response + * {@inheritdoc} */ public function add(Response $response) { @@ -45,26 +44,38 @@ public function add(Response $response) $this->ttls[] = $response->getTtl(); $this->maxAges[] = $response->getMaxAge(); } + + $this->embeddedResponses++; } /** - * Updates the Response HTTP headers based on the embedded Responses. - * - * @param Response $response + * {@inheritdoc} */ public function update(Response $response) { - // if we only have one Response, do nothing - if (1 === count($this->ttls)) { + // if we have no embedded Response, do nothing + if (0 === $this->embeddedResponses) { return; } + // Remove validation related headers in order to avoid browsers using + // their own cache, because some of the response content comes from + // at least one embedded response (which likely has a different caching strategy). + if ($response->isValidateable()) { + $response->setEtag(null); + $response->setLastModified(null); + $this->cacheable = false; + } + if (!$this->cacheable) { $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); return; } + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $response->getMaxAge(); + if (null !== $maxAge = min($this->maxAges)) { $response->setSharedMaxAge($maxAge); $response->headers->set('Age', $maxAge - min($this->ttls)); diff --git a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php index 69e938881449..3f37366a8489 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -205,10 +205,10 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ } if (null !== $this->esi) { - $this->esiCacheStrategy->add($response); - if (HttpKernelInterface::MASTER_REQUEST === $type) { $this->esiCacheStrategy->update($response); + } else { + $this->esiCacheStrategy->add($response); } } diff --git a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php index dc337789ad16..19ab1c799dcf 100644 --- a/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php +++ b/src/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -60,14 +60,7 @@ public function find($ip, $url, $limit, $method, $start = null, $end = null) fseek($file, 0, SEEK_END); $result = array(); - - for (;$limit > 0; $limit--) { - $line = $this->readLineFromFile($file); - - if (null === $line) { - break; - } - + while (count($result) < $limit && $line = $this->readLineFromFile($file)) { list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent) = str_getcsv($line); $csvTime = (int) $csvTime; diff --git a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php index 4cdd5f60e1f0..448ebc362bf3 100644 --- a/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -1075,4 +1075,32 @@ public function testXForwarderForHeaderForPassRequests() $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); } + + public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses() + { + $time = new \DateTime; + + $responses = array( + array( + 'status' => 200, + 'body' => '', + 'headers' => array( + 'Surrogate-Control' => 'content="ESI/1.0"', + 'ETag' => 'hey', + 'Last-Modified' => $time->format(DATE_RFC2822), + ), + ), + array( + 'status' => 200, + 'body' => 'Hey!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertNull($this->response->getETag()); + $this->assertNull($this->response->getLastModified()); + } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Profiler/AbstractProfilerStorageTest.php b/src/Symfony/Component/HttpKernel/Tests/Profiler/AbstractProfilerStorageTest.php index 7be88267a585..e78b07af87bc 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Profiler/AbstractProfilerStorageTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Profiler/AbstractProfilerStorageTest.php @@ -189,6 +189,21 @@ public function testRetrieveByEmptyUrlAndIp() $this->getStorage()->purge(); } + public function testRetrieveByMethodAndLimit() + { + foreach (array('POST', 'GET') as $method) { + for ($i = 0; $i < 5; $i++) { + $profile = new Profile('token_'.$i.$method); + $profile->setMethod($method); + $this->getStorage()->write($profile); + } + } + + $this->assertCount(5, $this->getStorage()->find('', '', 5, 'POST')); + + $this->getStorage()->purge(); + } + public function testPurge() { $profile = new Profile('token1');