|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* |
|
5
|
|
|
* This file is part of Phpfastcache. |
|
6
|
|
|
* |
|
7
|
|
|
* @license MIT License (MIT) |
|
8
|
|
|
* |
|
9
|
|
|
* For full copyright and license information, please see the docs/CREDITS.txt and LICENCE files. |
|
10
|
|
|
* |
|
11
|
|
|
* @author Georges.L (Geolim4) <[email protected]> |
|
12
|
|
|
* @author Contributors https://github.com/PHPSocialNetwork/phpfastcache/graphs/contributors |
|
13
|
|
|
*/ |
|
14
|
|
|
|
|
15
|
|
|
declare(strict_types=1); |
|
16
|
|
|
|
|
17
|
|
|
namespace Phpfastcache\Core\Pool\IO; |
|
18
|
|
|
|
|
19
|
|
|
use Phpfastcache\Config\IOConfigurationOptionInterface; |
|
20
|
|
|
use Phpfastcache\Core\Pool\TaggableCacheItemPoolTrait; |
|
21
|
|
|
use Phpfastcache\Entities\DriverStatistic; |
|
22
|
|
|
use Phpfastcache\Event\Event; |
|
23
|
|
|
use Phpfastcache\Exceptions\PhpfastcacheInvalidArgumentException; |
|
24
|
|
|
use Phpfastcache\Exceptions\PhpfastcacheIOException; |
|
25
|
|
|
use Phpfastcache\Util\Directory; |
|
26
|
|
|
use Phpfastcache\Util\SapiDetector; |
|
27
|
|
|
|
|
28
|
|
|
/** |
|
29
|
|
|
* @method IOConfigurationOptionInterface getConfig() |
|
30
|
|
|
*/ |
|
31
|
|
|
trait IOHelperTrait |
|
32
|
|
|
{ |
|
33
|
|
|
use TaggableCacheItemPoolTrait; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* @var array<string, string> |
|
37
|
|
|
*/ |
|
38
|
|
|
public array $tmp = []; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* Provide a generic getStats() method |
|
42
|
|
|
* for files-based drivers |
|
43
|
|
|
* @return DriverStatistic |
|
44
|
|
|
* @throws PhpfastcacheIOException |
|
45
|
|
|
* @throws PhpfastcacheInvalidArgumentException |
|
46
|
|
|
*/ |
|
47
|
|
|
public function getStats(): DriverStatistic |
|
48
|
|
|
{ |
|
49
|
|
|
$stat = new DriverStatistic(); |
|
50
|
|
|
$path = $this->getFilePath(false); |
|
51
|
|
|
|
|
52
|
|
|
if (!is_dir($path)) { |
|
53
|
|
|
throw new PhpfastcacheIOException("Can't read PATH:" . $path); |
|
54
|
|
|
} |
|
55
|
|
|
$stat->setSize(Directory::dirSize($path)) |
|
56
|
|
|
->setInfo('Number of files used to build the cache: ' . Directory::getFileCount($path)) |
|
57
|
|
|
->setRawData( |
|
58
|
|
|
[ |
|
59
|
|
|
'tmp' => $this->tmp, |
|
60
|
|
|
] |
|
61
|
|
|
); |
|
62
|
|
|
|
|
63
|
|
|
if ($this->getConfig()->isUseStaticItemCaching()) { |
|
64
|
|
|
$stat->setData(implode(', ', \array_keys($this->itemInstances))); |
|
|
|
|
|
|
65
|
|
|
} else { |
|
66
|
|
|
$stat->setData('No data available since static item caching option (useStaticItemCaching) is disabled.'); |
|
|
|
|
|
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
return $stat; |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* @param string|bool $keyword |
|
74
|
|
|
* @param bool $skip |
|
75
|
|
|
* @return string |
|
76
|
|
|
* @throws PhpfastcacheIOException |
|
77
|
|
|
* @throws PhpfastcacheInvalidArgumentException |
|
78
|
|
|
*/ |
|
79
|
|
|
protected function getFilePath(string|bool $keyword, bool $skip = false): string |
|
80
|
|
|
{ |
|
81
|
|
|
$path = $this->getPath(); |
|
82
|
|
|
|
|
83
|
|
|
if ($keyword === false) { |
|
84
|
|
|
return $path; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
$filename = $this->encodeFilename($keyword); |
|
|
|
|
|
|
88
|
|
|
$folder = \substr($filename, 0, 2) . DIRECTORY_SEPARATOR . \substr($filename, 2, 2); |
|
89
|
|
|
$path = \rtrim($path, '/\\') . DIRECTORY_SEPARATOR . $folder; |
|
90
|
|
|
|
|
91
|
|
|
/** |
|
92
|
|
|
* Skip Create Sub Folders; |
|
93
|
|
|
*/ |
|
94
|
|
|
if (!$skip && !\is_dir($path) && @!\mkdir($path, $this->getDefaultChmod(), true) && !\is_dir($path)) { |
|
95
|
|
|
throw new PhpfastcacheIOException( |
|
96
|
|
|
'Path "' . $path . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !' |
|
97
|
|
|
); |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
return $path . \DIRECTORY_SEPARATOR . $filename . '.' . $this->getConfig()->getCacheFileExtension(); |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
/** |
|
104
|
|
|
* @param bool $readonly |
|
105
|
|
|
* @return string |
|
106
|
|
|
* @throws PhpfastcacheIOException |
|
107
|
|
|
* @throws PhpfastcacheInvalidArgumentException |
|
108
|
|
|
*/ |
|
109
|
|
|
public function getPath(bool $readonly = false): string |
|
110
|
|
|
{ |
|
111
|
|
|
$tmpDir = \rtrim(\ini_get('upload_tmp_dir') ?: \sys_get_temp_dir(), '\\/') . DIRECTORY_SEPARATOR . 'phpfastcache'; |
|
112
|
|
|
$httpHost = $this->getConfig()->getSuperGlobalAccessor()('SERVER', 'HTTP_HOST'); |
|
113
|
|
|
$securityKey = $this->buildSecurityKey($httpHost); |
|
114
|
|
|
|
|
115
|
|
|
/** |
|
116
|
|
|
* Extends the temporary directory |
|
117
|
|
|
* with the security key and the driver name |
|
118
|
|
|
*/ |
|
119
|
|
|
$tmpDir = \rtrim($tmpDir, '/') . DIRECTORY_SEPARATOR; |
|
120
|
|
|
|
|
121
|
|
|
if (empty($this->getConfig()->getPath())) { |
|
122
|
|
|
$path = $tmpDir; |
|
123
|
|
|
} else { |
|
124
|
|
|
$path = \rtrim($this->getConfig()->getPath(), '/') . DIRECTORY_SEPARATOR; |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
$pathSuffix = $securityKey . DIRECTORY_SEPARATOR . $this->getDriverName(); |
|
128
|
|
|
$fullPath = Directory::getAbsolutePath($path . $pathSuffix); |
|
129
|
|
|
$fullPathTmp = Directory::getAbsolutePath($tmpDir . $pathSuffix); |
|
130
|
|
|
|
|
131
|
|
|
$this->mkdir($fullPath, $fullPathTmp); |
|
132
|
|
|
|
|
133
|
|
|
/** |
|
134
|
|
|
* In readonly mode we only attempt |
|
135
|
|
|
* to verify if the directory exists |
|
136
|
|
|
* or not, if it does not then we |
|
137
|
|
|
* return the temp dir |
|
138
|
|
|
*/ |
|
139
|
|
|
if ($readonly) { |
|
140
|
|
|
if ($this->getConfig()->isAutoTmpFallback() && (!@\file_exists($fullPath) || !@\is_writable($fullPath))) { |
|
141
|
|
|
return $fullPathTmp; |
|
142
|
|
|
} |
|
143
|
|
|
return $fullPath; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
return realpath($fullPath); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
protected function buildSecurityKey(?string $httpHost): string |
|
150
|
|
|
{ |
|
151
|
|
|
$securityKey = $this->getConfig()->getSecurityKey(); |
|
152
|
|
|
if (!$securityKey || \mb_strtolower($securityKey) === 'auto') { |
|
153
|
|
|
if (isset($httpHost)) { |
|
154
|
|
|
$securityKey = \preg_replace('/^www./', '', \strtolower(\str_replace(':', '_', $httpHost))); |
|
155
|
|
|
} else { |
|
156
|
|
|
$securityKey = (SapiDetector::isWebScript() ? 'web' : 'cli'); |
|
157
|
|
|
} |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
if (!empty($securityKey)) { |
|
161
|
|
|
$securityKey .= '/'; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
return static::cleanFileName($securityKey); |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
/** |
|
168
|
|
|
* @throws PhpfastcacheIOException |
|
169
|
|
|
*/ |
|
170
|
|
|
protected function mkdir(string $fullPath, string $fullPathTmp): void |
|
171
|
|
|
{ |
|
172
|
|
|
$fullPathHash = $this->getConfig()->getDefaultFileNameHashFunction()($fullPath); |
|
173
|
|
|
|
|
174
|
|
|
if (!isset($this->tmp[$fullPathHash]) || (!@\file_exists($fullPath) || !@\is_writable($fullPath))) { |
|
175
|
|
|
if (!@\file_exists($fullPath)) { |
|
176
|
|
|
if (@mkdir($fullPath, $this->getDefaultChmod(), true) === false && !\is_dir($fullPath)) { |
|
177
|
|
|
throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.'); |
|
178
|
|
|
} |
|
179
|
|
|
} elseif (!@\is_writable($fullPath) && !@\chmod($fullPath, $this->getDefaultChmod()) && $this->getConfig()->isAutoTmpFallback()) { |
|
180
|
|
|
/** |
|
181
|
|
|
* Switch back to tmp dir |
|
182
|
|
|
* again if the path is not writable |
|
183
|
|
|
*/ |
|
184
|
|
|
$fullPath = $fullPathTmp; |
|
185
|
|
|
if (!@\file_exists($fullPath) && @\mkdir($fullPath, $this->getDefaultChmod(), true) && !\is_dir($fullPath)) { |
|
186
|
|
|
throw new PhpfastcacheIOException('The directory ' . $fullPath . ' could not be created.'); |
|
187
|
|
|
} |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
/** |
|
191
|
|
|
* In case there is no directory |
|
192
|
|
|
* writable including the temporary |
|
193
|
|
|
* one, we must throw an exception |
|
194
|
|
|
*/ |
|
195
|
|
|
if (!@\file_exists($fullPath) || !@\is_writable($fullPath)) { |
|
196
|
|
|
throw new PhpfastcacheIOException( |
|
197
|
|
|
'Path "' . $fullPath . '" is not writable, please set a chmod 0777 or any writable permission and make sure to make use of an absolute path !' |
|
198
|
|
|
); |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
$this->tmp[$fullPathHash] = $fullPath; |
|
202
|
|
|
} |
|
203
|
|
|
} |
|
204
|
|
|
|
|
205
|
|
|
/** |
|
206
|
|
|
* @param string $filename |
|
207
|
|
|
* @return string |
|
208
|
|
|
*/ |
|
209
|
|
|
protected static function cleanFileName(string $filename): string |
|
210
|
|
|
{ |
|
211
|
|
|
$regex = [ |
|
212
|
|
|
'/[\?\[\]\/\\\=\<\>\:\;\,\'\"\&\$\#\*\(\)\|\~\`\!\{\}]/', |
|
213
|
|
|
'/\.$/', |
|
214
|
|
|
'/^\./', |
|
215
|
|
|
]; |
|
216
|
|
|
$replace = ['-', '', '']; |
|
217
|
|
|
|
|
218
|
|
|
return \trim(\preg_replace($regex, $replace, \trim($filename)), '-'); |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* @return int |
|
223
|
|
|
*/ |
|
224
|
|
|
protected function getDefaultChmod(): int |
|
225
|
|
|
{ |
|
226
|
|
|
if (!$this->getConfig()->getDefaultChmod()) { |
|
227
|
|
|
return 0777; |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
return $this->getConfig()->getDefaultChmod(); |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
/** |
|
234
|
|
|
* @param string $keyword |
|
235
|
|
|
* @return string |
|
236
|
|
|
*/ |
|
237
|
|
|
protected function encodeFilename(string $keyword): string |
|
238
|
|
|
{ |
|
239
|
|
|
return $this->getConfig()->getDefaultFileNameHashFunction()($keyword); |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* @param string $file |
|
244
|
|
|
* @return string |
|
245
|
|
|
* @throws PhpfastcacheIOException |
|
246
|
|
|
*/ |
|
247
|
|
|
protected function readFile(string $file): string |
|
248
|
|
|
{ |
|
249
|
|
|
if (!\is_readable($file)) { |
|
250
|
|
|
throw new PhpfastcacheIOException("Cannot read file located at: $file"); |
|
251
|
|
|
} |
|
252
|
|
|
if (\function_exists('file_get_contents')) { |
|
253
|
|
|
return (string)\file_get_contents($file); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
$string = ''; |
|
257
|
|
|
|
|
258
|
|
|
$fileHandle = @\fopen($file, 'rb'); |
|
259
|
|
|
while (!\feof($fileHandle)) { |
|
|
|
|
|
|
260
|
|
|
$line = \fgets($fileHandle); |
|
|
|
|
|
|
261
|
|
|
$string .= $line; |
|
262
|
|
|
} |
|
263
|
|
|
\fclose($fileHandle); |
|
|
|
|
|
|
264
|
|
|
|
|
265
|
|
|
return $string; |
|
266
|
|
|
} |
|
267
|
|
|
|
|
268
|
|
|
/******************** |
|
269
|
|
|
* |
|
270
|
|
|
* PSR-6 Extended Methods |
|
271
|
|
|
* |
|
272
|
|
|
*******************/ |
|
273
|
|
|
|
|
274
|
|
|
/** |
|
275
|
|
|
* @param string $file |
|
276
|
|
|
* @param string $data |
|
277
|
|
|
* @param bool $secureFileManipulation |
|
278
|
|
|
* @return bool |
|
279
|
|
|
* @throws PhpfastcacheIOException |
|
280
|
|
|
* @throws \Exception |
|
281
|
|
|
*/ |
|
282
|
|
|
protected function writeFile(string $file, string $data, bool $secureFileManipulation = false): bool |
|
283
|
|
|
{ |
|
284
|
|
|
$this->eventManager->dispatch(Event::CACHE_WRITE_FILE_ON_DISK, $this, $file, $secureFileManipulation); |
|
285
|
|
|
|
|
286
|
|
|
if ($secureFileManipulation) { |
|
287
|
|
|
$tmpFilename = Directory::getAbsolutePath( |
|
288
|
|
|
dirname($file) . \DIRECTORY_SEPARATOR . 'tmp_' . $this->getConfig()->getDefaultFileNameHashFunction()( |
|
289
|
|
|
\bin2hex(\random_bytes(16)) |
|
290
|
|
|
) |
|
291
|
|
|
) . '.' . $this->getConfig()->getCacheFileExtension() . \random_int(1000, 9999); |
|
292
|
|
|
|
|
293
|
|
|
$handle = \fopen($tmpFilename, 'w+b'); |
|
294
|
|
|
if (\is_resource($handle)) { |
|
295
|
|
|
\flock($handle, \LOCK_EX); |
|
296
|
|
|
$octetWritten = fwrite($handle, $data); |
|
297
|
|
|
\flock($handle, \LOCK_UN); |
|
298
|
|
|
\fclose($handle); |
|
299
|
|
|
} |
|
300
|
|
|
|
|
301
|
|
|
if (!\rename($tmpFilename, $file)) { |
|
302
|
|
|
throw new PhpfastcacheIOException(\sprintf('Failed to rename %s to %s', $tmpFilename, $file)); |
|
303
|
|
|
} |
|
304
|
|
|
} else { |
|
305
|
|
|
$handle = \fopen($file, 'w+b'); |
|
306
|
|
|
if (\is_resource($handle)) { |
|
307
|
|
|
$octetWritten = \fwrite($handle, $data); |
|
308
|
|
|
\fclose($handle); |
|
309
|
|
|
} |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
return (bool)($octetWritten ?? false); |
|
313
|
|
|
} |
|
314
|
|
|
} |
|
315
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.