[ Index ] |
PHP Cross Reference of phpBB-3.3.12-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * This code is partially based on the Rack-Cache library by Ryan Tomayko, 9 * which is released under the MIT license. 10 * 11 * For the full copyright and license information, please view the LICENSE 12 * file that was distributed with this source code. 13 */ 14 15 namespace Symfony\Component\HttpKernel\HttpCache; 16 17 use Symfony\Component\HttpFoundation\Request; 18 use Symfony\Component\HttpFoundation\Response; 19 20 /** 21 * Store implements all the logic for storing cache metadata (Request and Response headers). 22 * 23 * @author Fabien Potencier <fabien@symfony.com> 24 */ 25 class Store implements StoreInterface 26 { 27 protected $root; 28 private $keyCache; 29 private $locks; 30 31 /** 32 * @param string $root The path to the cache directory 33 * 34 * @throws \RuntimeException 35 */ 36 public function __construct($root) 37 { 38 $this->root = $root; 39 if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { 40 throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); 41 } 42 $this->keyCache = new \SplObjectStorage(); 43 $this->locks = []; 44 } 45 46 /** 47 * Cleanups storage. 48 */ 49 public function cleanup() 50 { 51 // unlock everything 52 foreach ($this->locks as $lock) { 53 flock($lock, \LOCK_UN); 54 fclose($lock); 55 } 56 57 $this->locks = []; 58 } 59 60 /** 61 * Tries to lock the cache for a given Request, without blocking. 62 * 63 * @return bool|string true if the lock is acquired, the path to the current lock otherwise 64 */ 65 public function lock(Request $request) 66 { 67 $key = $this->getCacheKey($request); 68 69 if (!isset($this->locks[$key])) { 70 $path = $this->getPath($key); 71 if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { 72 return $path; 73 } 74 $h = fopen($path, 'cb'); 75 if (!flock($h, \LOCK_EX | \LOCK_NB)) { 76 fclose($h); 77 78 return $path; 79 } 80 81 $this->locks[$key] = $h; 82 } 83 84 return true; 85 } 86 87 /** 88 * Releases the lock for the given Request. 89 * 90 * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise 91 */ 92 public function unlock(Request $request) 93 { 94 $key = $this->getCacheKey($request); 95 96 if (isset($this->locks[$key])) { 97 flock($this->locks[$key], \LOCK_UN); 98 fclose($this->locks[$key]); 99 unset($this->locks[$key]); 100 101 return true; 102 } 103 104 return false; 105 } 106 107 public function isLocked(Request $request) 108 { 109 $key = $this->getCacheKey($request); 110 111 if (isset($this->locks[$key])) { 112 return true; // shortcut if lock held by this process 113 } 114 115 if (!file_exists($path = $this->getPath($key))) { 116 return false; 117 } 118 119 $h = fopen($path, 'rb'); 120 flock($h, \LOCK_EX | \LOCK_NB, $wouldBlock); 121 flock($h, \LOCK_UN); // release the lock we just acquired 122 fclose($h); 123 124 return (bool) $wouldBlock; 125 } 126 127 /** 128 * Locates a cached Response for the Request provided. 129 * 130 * @return Response|null A Response instance, or null if no cache entry was found 131 */ 132 public function lookup(Request $request) 133 { 134 $key = $this->getCacheKey($request); 135 136 if (!$entries = $this->getMetadata($key)) { 137 return null; 138 } 139 140 // find a cached entry that matches the request. 141 $match = null; 142 foreach ($entries as $entry) { 143 if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { 144 $match = $entry; 145 146 break; 147 } 148 } 149 150 if (null === $match) { 151 return null; 152 } 153 154 $headers = $match[1]; 155 if (file_exists($path = $this->getPath($headers['x-content-digest'][0]))) { 156 return $this->restoreResponse($headers, $path); 157 } 158 159 // TODO the metaStore referenced an entity that doesn't exist in 160 // the entityStore. We definitely want to return nil but we should 161 // also purge the entry from the meta-store when this is detected. 162 return null; 163 } 164 165 /** 166 * Writes a cache entry to the store for the given Request and Response. 167 * 168 * Existing entries are read and any that match the response are removed. This 169 * method calls write with the new list of cache entries. 170 * 171 * @return string The key under which the response is stored 172 * 173 * @throws \RuntimeException 174 */ 175 public function write(Request $request, Response $response) 176 { 177 $key = $this->getCacheKey($request); 178 $storedEnv = $this->persistRequest($request); 179 180 if ($response->headers->has('X-Body-File')) { 181 // Assume the response came from disk, but at least perform some safeguard checks 182 if (!$response->headers->has('X-Content-Digest')) { 183 throw new \RuntimeException('A restored response must have the X-Content-Digest header.'); 184 } 185 186 $digest = $response->headers->get('X-Content-Digest'); 187 if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) { 188 throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.'); 189 } 190 // Everything seems ok, omit writing content to disk 191 } else { 192 $digest = $this->generateContentDigest($response); 193 $response->headers->set('X-Content-Digest', $digest); 194 195 if (!$this->save($digest, $response->getContent(), false)) { 196 throw new \RuntimeException('Unable to store the entity.'); 197 } 198 199 if (!$response->headers->has('Transfer-Encoding')) { 200 $response->headers->set('Content-Length', \strlen($response->getContent())); 201 } 202 } 203 204 // read existing cache entries, remove non-varying, and add this one to the list 205 $entries = []; 206 $vary = $response->headers->get('vary'); 207 foreach ($this->getMetadata($key) as $entry) { 208 if (!isset($entry[1]['vary'][0])) { 209 $entry[1]['vary'] = ['']; 210 } 211 212 if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { 213 $entries[] = $entry; 214 } 215 } 216 217 $headers = $this->persistResponse($response); 218 unset($headers['age']); 219 220 array_unshift($entries, [$storedEnv, $headers]); 221 222 if (!$this->save($key, serialize($entries))) { 223 throw new \RuntimeException('Unable to store the metadata.'); 224 } 225 226 return $key; 227 } 228 229 /** 230 * Returns content digest for $response. 231 * 232 * @return string 233 */ 234 protected function generateContentDigest(Response $response) 235 { 236 return 'en'.hash('sha256', $response->getContent()); 237 } 238 239 /** 240 * Invalidates all cache entries that match the request. 241 * 242 * @throws \RuntimeException 243 */ 244 public function invalidate(Request $request) 245 { 246 $modified = false; 247 $key = $this->getCacheKey($request); 248 249 $entries = []; 250 foreach ($this->getMetadata($key) as $entry) { 251 $response = $this->restoreResponse($entry[1]); 252 if ($response->isFresh()) { 253 $response->expire(); 254 $modified = true; 255 $entries[] = [$entry[0], $this->persistResponse($response)]; 256 } else { 257 $entries[] = $entry; 258 } 259 } 260 261 if ($modified && !$this->save($key, serialize($entries))) { 262 throw new \RuntimeException('Unable to store the metadata.'); 263 } 264 } 265 266 /** 267 * Determines whether two Request HTTP header sets are non-varying based on 268 * the vary response header value provided. 269 * 270 * @param string $vary A Response vary header 271 * @param array $env1 A Request HTTP header array 272 * @param array $env2 A Request HTTP header array 273 * 274 * @return bool true if the two environments match, false otherwise 275 */ 276 private function requestsMatch($vary, $env1, $env2) 277 { 278 if (empty($vary)) { 279 return true; 280 } 281 282 foreach (preg_split('/[\s,]+/', $vary) as $header) { 283 $key = str_replace('_', '-', strtolower($header)); 284 $v1 = isset($env1[$key]) ? $env1[$key] : null; 285 $v2 = isset($env2[$key]) ? $env2[$key] : null; 286 if ($v1 !== $v2) { 287 return false; 288 } 289 } 290 291 return true; 292 } 293 294 /** 295 * Gets all data associated with the given key. 296 * 297 * Use this method only if you know what you are doing. 298 * 299 * @param string $key The store key 300 * 301 * @return array An array of data associated with the key 302 */ 303 private function getMetadata($key) 304 { 305 if (!$entries = $this->load($key)) { 306 return []; 307 } 308 309 return unserialize($entries); 310 } 311 312 /** 313 * Purges data for the given URL. 314 * 315 * This method purges both the HTTP and the HTTPS version of the cache entry. 316 * 317 * @param string $url A URL 318 * 319 * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise 320 */ 321 public function purge($url) 322 { 323 $http = preg_replace('#^https:#', 'http:', $url); 324 $https = preg_replace('#^http:#', 'https:', $url); 325 326 $purgedHttp = $this->doPurge($http); 327 $purgedHttps = $this->doPurge($https); 328 329 return $purgedHttp || $purgedHttps; 330 } 331 332 /** 333 * Purges data for the given URL. 334 * 335 * @param string $url A URL 336 * 337 * @return bool true if the URL exists and has been purged, false otherwise 338 */ 339 private function doPurge($url) 340 { 341 $key = $this->getCacheKey(Request::create($url)); 342 if (isset($this->locks[$key])) { 343 flock($this->locks[$key], \LOCK_UN); 344 fclose($this->locks[$key]); 345 unset($this->locks[$key]); 346 } 347 348 if (file_exists($path = $this->getPath($key))) { 349 unlink($path); 350 351 return true; 352 } 353 354 return false; 355 } 356 357 /** 358 * Loads data for the given key. 359 * 360 * @param string $key The store key 361 * 362 * @return string|null The data associated with the key 363 */ 364 private function load($key) 365 { 366 $path = $this->getPath($key); 367 368 return file_exists($path) && false !== ($contents = file_get_contents($path)) ? $contents : null; 369 } 370 371 /** 372 * Save data for the given key. 373 * 374 * @param string $key The store key 375 * @param string $data The data to store 376 * @param bool $overwrite Whether existing data should be overwritten 377 * 378 * @return bool 379 */ 380 private function save($key, $data, $overwrite = true) 381 { 382 $path = $this->getPath($key); 383 384 if (!$overwrite && file_exists($path)) { 385 return true; 386 } 387 388 if (isset($this->locks[$key])) { 389 $fp = $this->locks[$key]; 390 @ftruncate($fp, 0); 391 @fseek($fp, 0); 392 $len = @fwrite($fp, $data); 393 if (\strlen($data) !== $len) { 394 @ftruncate($fp, 0); 395 396 return false; 397 } 398 } else { 399 if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { 400 return false; 401 } 402 403 $tmpFile = tempnam(\dirname($path), basename($path)); 404 if (false === $fp = @fopen($tmpFile, 'wb')) { 405 @unlink($tmpFile); 406 407 return false; 408 } 409 @fwrite($fp, $data); 410 @fclose($fp); 411 412 if ($data != file_get_contents($tmpFile)) { 413 @unlink($tmpFile); 414 415 return false; 416 } 417 418 if (false === @rename($tmpFile, $path)) { 419 @unlink($tmpFile); 420 421 return false; 422 } 423 } 424 425 @chmod($path, 0666 & ~umask()); 426 427 return true; 428 } 429 430 public function getPath($key) 431 { 432 return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); 433 } 434 435 /** 436 * Generates a cache key for the given Request. 437 * 438 * This method should return a key that must only depend on a 439 * normalized version of the request URI. 440 * 441 * If the same URI can have more than one representation, based on some 442 * headers, use a Vary header to indicate them, and each representation will 443 * be stored independently under the same cache key. 444 * 445 * @return string A key for the given Request 446 */ 447 protected function generateCacheKey(Request $request) 448 { 449 return 'md'.hash('sha256', $request->getUri()); 450 } 451 452 /** 453 * Returns a cache key for the given Request. 454 * 455 * @return string A key for the given Request 456 */ 457 private function getCacheKey(Request $request) 458 { 459 if (isset($this->keyCache[$request])) { 460 return $this->keyCache[$request]; 461 } 462 463 return $this->keyCache[$request] = $this->generateCacheKey($request); 464 } 465 466 /** 467 * Persists the Request HTTP headers. 468 * 469 * @return array An array of HTTP headers 470 */ 471 private function persistRequest(Request $request) 472 { 473 return $request->headers->all(); 474 } 475 476 /** 477 * Persists the Response HTTP headers. 478 * 479 * @return array An array of HTTP headers 480 */ 481 private function persistResponse(Response $response) 482 { 483 $headers = $response->headers->all(); 484 $headers['X-Status'] = [$response->getStatusCode()]; 485 486 return $headers; 487 } 488 489 /** 490 * Restores a Response from the HTTP headers and body. 491 * 492 * @param array $headers An array of HTTP headers for the Response 493 * @param string $path Path to the Response body 494 * 495 * @return Response 496 */ 497 private function restoreResponse($headers, $path = null) 498 { 499 $status = $headers['X-Status'][0]; 500 unset($headers['X-Status']); 501 502 if (null !== $path) { 503 $headers['X-Body-File'] = [$path]; 504 } 505 506 return new Response($path, $status, $headers); 507 } 508 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Jun 23 12:25:44 2024 | Cross-referenced by PHPXref 0.7.1 |