PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Filesystem / PathIdentifier.php
wp-staging / Framework / Filesystem Last commit date
Filters 1 day ago Scanning 1 day ago AbstractFileObject.php 1 day ago AbstractFilesystemScanner.php 1 day ago DebugLogReader.php 1 day ago DirectoryListing.php 1 day ago DirectorySize.php 1 day ago DiskWriteCheck.php 1 day ago FileObject.php 1 day ago Filesystem.php 1 day ago FilesystemExceptions.php 5 years ago FilesystemScanner.php 1 day ago FilesystemScannerDto.php 1 day ago FilterableDirectoryIterator.php 1 day ago LegacyFileRulesTrait.php 1 day ago LogCleanup.php 1 day ago LogFiles.php 1 day ago MissingFileException.php 3 years ago OPcache.php 1 day ago PartIdentifier.php 1 day ago PathChecker.php 1 day ago PathIdentifier.php 1 day ago Permissions.php 1 day ago WpUploadsFolderSymlinker.php 1 day ago
PathIdentifier.php
375 lines
1 <?php
2
3 namespace WPStaging\Framework\Filesystem;
4
5 use WPStaging\Framework\Adapter\Directory;
6 use WPStaging\Framework\Adapter\DirectoryInterface;
7
8
9
10
11
12
13
14
15
16
17
18 class PathIdentifier
19 {
20
21 const IDENTIFIER_ABSPATH = 'wpstg_a_';
22
23
24 const IDENTIFIER_WP_CONTENT = 'wpstg_c_';
25
26
27 const IDENTIFIER_PLUGINS = 'wpstg_p_';
28
29
30 const IDENTIFIER_THEMES = 'wpstg_t_';
31
32
33 const IDENTIFIER_MUPLUGINS = 'wpstg_m_';
34
35
36 const IDENTIFIER_UPLOADS = 'wpstg_u_';
37
38
39 const IDENTIFIER_LANG = 'wpstg_l_';
40
41
42
43
44
45
46 protected $lastIdentifier;
47
48
49 protected $directory;
50
51 public function __construct(DirectoryInterface $directory)
52 {
53 $this->directory = $directory;
54 }
55
56
57 public function getBackupDirectory()
58 {
59 return $this->directory->getBackupDirectory();
60 }
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 public function transformPathToIdentifiable($path)
77 {
78
79 if (isset($this->lastIdentifier) && $this->lastIdentifier !== self::IDENTIFIER_WP_CONTENT) {
80 $basePath = $this->getIdentifierPath($this->lastIdentifier);
81
82
83 if (strpos($path, $basePath) === 0) {
84 return $this->lastIdentifier . substr($path, strlen($basePath));
85 }
86 }
87
88
89 if (strpos($path, $this->directory->getUploadsDirectory()) === 0) {
90 $this->lastIdentifier = self::IDENTIFIER_UPLOADS;
91
92 return $this->lastIdentifier . substr($path, strlen($this->directory->getUploadsDirectory()));
93 }
94
95 if ($this->directory->getPluginUploadsDirectory() !== $this->directory->getUploadsDirectory()) {
96 if (strpos($path, $this->directory->getPluginUploadsDirectory()) === 0) {
97 $this->lastIdentifier = self::IDENTIFIER_UPLOADS;
98
99 return $this->lastIdentifier . substr($path, strlen($this->directory->getPluginUploadsDirectory()));
100 }
101 }
102
103 if (strpos($path, $this->directory->getPluginsDirectory()) === 0) {
104 $this->lastIdentifier = self::IDENTIFIER_PLUGINS;
105
106 return $this->lastIdentifier . substr($path, strlen($this->directory->getPluginsDirectory()));
107 }
108
109 foreach ($this->directory->getAllThemesDirectories() as $themesDirectory) {
110 if (strpos($path, $themesDirectory) === 0) {
111 $this->lastIdentifier = self::IDENTIFIER_THEMES;
112
113 return $this->lastIdentifier . substr($path, strlen($themesDirectory));
114 }
115 }
116
117 if (strpos($path, $this->directory->getMuPluginsDirectory()) === 0) {
118 $this->lastIdentifier = self::IDENTIFIER_MUPLUGINS;
119
120 return $this->lastIdentifier . substr($path, strlen($this->directory->getMuPluginsDirectory()));
121 }
122
123 if (strpos($path, $this->directory->getLangsDirectory()) === 0) {
124 $this->lastIdentifier = self::IDENTIFIER_LANG;
125
126 return $this->lastIdentifier . substr($path, strlen($this->directory->getLangsDirectory()));
127 }
128
129 if (strpos($path, $this->directory->getWpContentDirectory()) === 0) {
130 $this->lastIdentifier = self::IDENTIFIER_WP_CONTENT;
131
132 return $this->lastIdentifier . substr($path, strlen($this->directory->getWpContentDirectory()));
133 }
134
135 if (strpos($path, $this->directory->getAbspath()) === 0) {
136 $this->lastIdentifier = self::IDENTIFIER_ABSPATH;
137
138 return $this->lastIdentifier . substr($path, strlen($this->directory->getAbspath()));
139 }
140
141
142 throw new \RuntimeException(sprintf(
143 'Could not classify %s for backup: it is not inside any known WordPress content directory (plugins, themes, mu-plugins, uploads, languages, wp-content, or the WordPress root).',
144 $path === '' ? 'an empty path' : "the path \"$path\""
145 ));
146 }
147
148
149
150
151
152
153 public function transformIdentifiableToPath($path)
154 {
155 $identifier = $this->getIdentifierFromPath($path);
156 $pathWithoutIdentifier = $this->getPathWithoutIdentifier($path);
157
158 return $this->getIdentifierPath($identifier) . $pathWithoutIdentifier;
159 }
160
161
162
163
164
165
166 public function getPathWithoutIdentifier($path)
167 {
168 return substr($path, 8);
169 }
170
171
172
173
174
175
176 public function hasPathTraversal(string $identifiablePath): bool
177 {
178 $relativePath = $this->getPathWithoutIdentifier($identifiablePath);
179 if ($relativePath === '') {
180 return true;
181 }
182
183 if (strpos($relativePath, "\0") !== false) {
184 return true;
185 }
186
187 $normalizedPath = str_replace('\\', '/', $relativePath);
188 if ($normalizedPath[0] === '/' || preg_match('#^[a-zA-Z]:#', $normalizedPath) === 1) {
189 return true;
190 }
191
192 return in_array('..', explode('/', $normalizedPath), true);
193 }
194
195 public function isPathWithinRoot(string $targetPath, string $root): bool
196 {
197 $normalizedTarget = str_replace('\\', '/', $targetPath);
198 if (strpos($normalizedTarget, "\0") !== false || in_array('..', explode('/', $normalizedTarget), true)) {
199 return false;
200 }
201
202 $realRoot = realpath($root);
203 if ($realRoot === false) {
204 return false;
205 }
206
207 if (is_link($targetPath)) {
208 return false;
209 }
210
211 $deepestExisting = $targetPath;
212 while (!file_exists($deepestExisting)) {
213 if (is_link($deepestExisting)) {
214 return false;
215 }
216
217 $parent = dirname($deepestExisting);
218 if ($parent === $deepestExisting) {
219 return false;
220 }
221
222 $deepestExisting = $parent;
223 }
224
225 $realExisting = realpath($deepestExisting);
226 if ($realExisting === false) {
227 return false;
228 }
229
230 $realExisting = rtrim($realExisting, '/\\') . DIRECTORY_SEPARATOR;
231 $realRoot = rtrim($realRoot, '/\\') . DIRECTORY_SEPARATOR;
232
233 return strpos($realExisting, $realRoot) === 0;
234 }
235
236
237
238
239
240
241 public function getIdentifierFromPath($path)
242 {
243 return substr($path, 0, 8);
244 }
245
246
247
248
249 public function transformIdentifiableToRelativePath(string $string): string
250 {
251 $string = trim($string);
252 if (empty($string)) {
253 return $string;
254 }
255
256 $key = substr($string, 0, 8);
257 $path = $this->getRelativePath($key);
258 if ($path !== $key && is_string($path)) {
259 return substr_replace($string, $path, 0, 8);
260 }
261
262 return $string;
263 }
264
265
266
267
268 public function getRelativePath(string $identifier): string
269 {
270 static $cache = [];
271
272 if (!empty($cache) && !empty($identifier) && isset($cache[$identifier])) {
273 return $cache[$identifier];
274 }
275
276 $path = [
277 self::IDENTIFIER_ABSPATH => '',
278 self::IDENTIFIER_WP_CONTENT => 'wp-content/',
279 self::IDENTIFIER_PLUGINS => 'wp-content/plugins/',
280 self::IDENTIFIER_THEMES => 'wp-content/themes/',
281 self::IDENTIFIER_MUPLUGINS => 'wp-content/mu-plugins/',
282 self::IDENTIFIER_UPLOADS => 'wp-content/uploads/',
283 self::IDENTIFIER_LANG => 'wp-content/languages/',
284 ];
285
286 if (!empty($identifier) && isset($path[$identifier])) {
287 $cache[$identifier] = $path[$identifier];
288 return $cache[$identifier];
289 }
290
291
292 trigger_error(sprintf('[%s] Could not find a path for the placeholder: %s', __METHOD__, filter_var($identifier, FILTER_SANITIZE_SPECIAL_CHARS)));
293 return $identifier;
294 }
295
296 public function getAbsolutePath(string $identifier): string
297 {
298 return $this->getIdentifierPath($identifier);
299 }
300
301
302
303
304 public function getIdentifierByPartName(string $key): string
305 {
306 static $cache = [];
307
308 if (!empty($cache) && !empty($key) && !empty($cache[$key])) {
309 return $cache[$key];
310 }
311
312 $list = [
313 PartIdentifier::WP_CONTENT_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_WP_CONTENT,
314 PartIdentifier::PLUGIN_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_PLUGINS,
315 PartIdentifier::THEME_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_THEMES,
316 PartIdentifier::MU_PLUGIN_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_MUPLUGINS,
317 PartIdentifier::UPLOAD_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_UPLOADS,
318 PartIdentifier::LANGUAGE_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_LANG,
319 PartIdentifier::DATABASE_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_UPLOADS,
320 PartIdentifier::WP_ROOT_PART_IDENTIFIER => PathIdentifier::IDENTIFIER_ABSPATH,
321 ];
322
323 if (!empty($key) && !empty($list[$key])) {
324 $cache[$key] = $list[$key];
325 return $cache[$key];
326 }
327
328 return '';
329 }
330
331
332
333
334
335
336 protected function getIdentifierPath($identifier)
337 {
338
339 switch ($identifier) {
340 case self::IDENTIFIER_ABSPATH:
341 return $this->directory->getAbspath();
342 case self::IDENTIFIER_UPLOADS:
343 return $this->directory->getUploadsDirectory();
344 case self::IDENTIFIER_PLUGINS:
345 return $this->directory->getPluginsDirectory();
346 case self::IDENTIFIER_THEMES:
347 return $this->directory->getActiveThemeParentDirectory();
348 case self::IDENTIFIER_MUPLUGINS:
349 return $this->directory->getMuPluginsDirectory();
350 case self::IDENTIFIER_LANG:
351 return $this->directory->getLangsDirectory();
352 case self::IDENTIFIER_WP_CONTENT:
353 return $this->directory->getWpContentDirectory();
354 default:
355 throw new \UnexpectedValueException(sprintf("[%s] Could not find a path for the placeholder: %s", __METHOD__, filter_var($identifier, FILTER_SANITIZE_SPECIAL_CHARS)));
356 }
357 }
358
359
360
361
362
363
364 public function hasDropinsFile(string $identifiablePath): bool
365 {
366 if (!(strpos($identifiablePath, self::IDENTIFIER_WP_CONTENT) === 0)) {
367 return false;
368 }
369
370 $dropinsFile = implode('|', PartIdentifier::DROP_IN_FILES);
371
372 return preg_match('@^' . self::IDENTIFIER_WP_CONTENT . '(' . $dropinsFile . ')@', $identifiablePath) ? true : false;
373 }
374 }
375