Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Store.php

Zuletzt modifiziert: 01.05.2026, 11:26 - Dateigröße: 14.38 KiB


 001  <?php
 002   
 003  /*
 004   * This file is part of the Symfony package.
 005   *
 006   * (c) Fabien Potencier <fabien@symfony.com>
 007   *
 008   * This code is partially based on the Rack-Cache library by Ryan Tomayko,
 009   * which is released under the MIT license.
 010   *
 011   * For the full copyright and license information, please view the LICENSE
 012   * file that was distributed with this source code.
 013   */
 014   
 015  namespace Symfony\Component\HttpKernel\HttpCache;
 016   
 017  use Symfony\Component\HttpFoundation\Request;
 018  use Symfony\Component\HttpFoundation\Response;
 019   
 020  /**
 021   * Store implements all the logic for storing cache metadata (Request and Response headers).
 022   *
 023   * @author Fabien Potencier <fabien@symfony.com>
 024   */
 025  class Store implements StoreInterface
 026  {
 027      protected $root;
 028      private $keyCache;
 029      private $locks;
 030   
 031      /**
 032       * @param string $root The path to the cache directory
 033       *
 034       * @throws \RuntimeException
 035       */
 036      public function __construct($root)
 037      {
 038          $this->root = $root;
 039          if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) {
 040              throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root));
 041          }
 042          $this->keyCache = new \SplObjectStorage();
 043          $this->locks = [];
 044      }
 045   
 046      /**
 047       * Cleanups storage.
 048       */
 049      public function cleanup()
 050      {
 051          // unlock everything
 052          foreach ($this->locks as $lock) {
 053              flock($lock, \LOCK_UN);
 054              fclose($lock);
 055          }
 056   
 057          $this->locks = [];
 058      }
 059   
 060      /**
 061       * Tries to lock the cache for a given Request, without blocking.
 062       *
 063       * @return bool|string true if the lock is acquired, the path to the current lock otherwise
 064       */
 065      public function lock(Request $request)
 066      {
 067          $key = $this->getCacheKey($request);
 068   
 069          if (!isset($this->locks[$key])) {
 070              $path = $this->getPath($key);
 071              if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) {
 072                  return $path;
 073              }
 074              $h = fopen($path, 'cb');
 075              if (!flock($h, \LOCK_EX | \LOCK_NB)) {
 076                  fclose($h);
 077   
 078                  return $path;
 079              }
 080   
 081              $this->locks[$key] = $h;
 082          }
 083   
 084          return true;
 085      }
 086   
 087      /**
 088       * Releases the lock for the given Request.
 089       *
 090       * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise
 091       */
 092      public function unlock(Request $request)
 093      {
 094          $key = $this->getCacheKey($request);
 095   
 096          if (isset($this->locks[$key])) {
 097              flock($this->locks[$key], \LOCK_UN);
 098              fclose($this->locks[$key]);
 099              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  }
 509