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. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Store.php
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