Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

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: 02.04.2025, 15:03 - 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