PluginProbe
The WP Remote WordPress Plugin / 6.72
The WP Remote WordPress Plugin v6.72
6.72 6.69 6.65 6.62 6.48 6.47 4.87 4.97 5.05 5.09 5.16 5.22 5.24 5.25 5.38 5.41 5.42 5.45 5.47 5.53 5.56 5.65 5.68 5.72 5.73 All 53 releases
wpremote / callback / wings / fs.php

fs.php in The WP Remote WordPress Plugin 6.72, at callback/wings/fs.php

551 lines 15.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) exit;
3 if (!class_exists('WPRFSCallback')) :
4 require_once dirname( __FILE__ ) . '/../streams.php';
5
6 class WPRFSCallback extends WPRCallbackBase {
7 public $stream;
8 public $account;
9
10 public static $cwAllowedFiles = array(".htaccess", ".user.ini", "malcare-waf.php");
11 const FS_WING_VERSION = 1.4;
12
13 public function __construct($callback_handler) {
14 $this->account = $callback_handler->account;
15 }
16
17 function fileStat($relfile, $md5 = false) {
18 $absfile = ABSPATH.$relfile;
19 $fdata = array();
20 $fdata["filename"] = $relfile;
21
22 if (@is_readable($absfile) === false) {
23 $fdata["failed"] = true;
24 $fdata["error"] = "NOT_READABLE";
25 return $fdata;
26 }
27
28 $stats = @stat($absfile);
29 if ($stats) {
30 foreach (preg_grep('#size|uid|gid|mode|mtime#i', array_keys($stats)) as $key ) {
31 $fdata[$key] = $stats[$key];
32 }
33 if (is_link($absfile)) {
34 $fdata["link"] = @readlink($absfile);
35 }
36 if ($md5 === true && !is_dir($absfile)) {
37 $fdata["md5"] = $this->calculateMd5($absfile, array(), 0, 0, 0);
38 }
39 } else {
40 $fdata["failed"] = true;
41 }
42 return $fdata;
43 }
44
45 function scanFilesUsingGlob($initdir = "./", $offset = 0, $limit = 0, $bsize = 512, $recurse = true, $regex = '{.??,}*') {
46 $i = 0;
47 $dirs = array();
48 $dirs[] = $initdir;
49 $bfc = 0;
50 $bfa = array();
51 $current = 0;
52 $abspath = realpath(ABSPATH).'/';
53 $abslen = strlen($abspath);
54 # XNOTE: $recurse cannot be used directly here
55 while ($i < count($dirs)) {
56 $dir = $dirs[$i];
57
58 foreach (glob($abspath.$dir.$regex, GLOB_NOSORT | GLOB_BRACE) as $absfile) {
59 $relfile = substr($absfile, $abslen);
60 if (is_dir($absfile) && !is_link($absfile)) {
61 $dirs[] = $relfile."/";
62 }
63 $current++;
64 if ($offset >= $current)
65 continue;
66 if (($limit != 0) && (($current - $offset) > $limit)) {
67 $i = count($dirs);
68 break;
69 }
70 $bfa[] = $this->fileStat($relfile);
71 $bfc++;
72 if ($bfc == $bsize) {
73 $str = serialize($bfa);
74 $this->stream->writeStream($str);
75 $bfc = 0;
76 $bfa = array();
77 }
78 }
79 $regex = '{.??,}*';
80 $i++;
81 if ($recurse == false)
82 break;
83 }
84 if ($bfc != 0) {
85 $str = serialize($bfa);
86 $this->stream->writeStream($str);
87 }
88 return array("status" => "done");
89 }
90
91 function scanFiles($initdir = "./", $offset = 0, $limit = 0, $bsize = 512, $recurse = true, $md5 = false) {
92 $i = 0;
93 $links = array();
94 $dirs = array();
95 $dirs[] = $initdir;
96 $bfc = 0;
97 $bfa = array();
98 $current = 0;
99 while ($i < count($dirs)) {
100 $dir = $dirs[$i];
101 $d = @opendir(ABSPATH.$dir);
102 if ($d) {
103 while (($file = readdir($d)) !== false) {
104 if ($file == '.' || $file == '..') { continue; }
105 $relfile = $dir.$file;
106 $absfile = ABSPATH.$relfile;
107 if (is_link($absfile)) {
108 $links[] = $relfile;
109 }
110 if (is_dir($absfile) && !is_link($absfile)) {
111 $dirs[] = $relfile."/";
112 }
113 $current++;
114 if ($offset >= $current)
115 continue;
116 if (($limit != 0) && (($current - $offset) > $limit)) {
117 $i = count($dirs);
118 break;
119 }
120 $bfa[] = $this->fileStat($relfile, $md5);
121 $bfc++;
122 if ($bfc == $bsize) {
123 $str = serialize($bfa);
124 $this->stream->writeStream($str);
125 $bfc = 0;
126 $bfa = array();
127 }
128 }
129 closedir($d);
130 }
131 $i++;
132 if ($recurse == false)
133 break;
134 }
135 if ($bfc != 0) {
136 $str = serialize($bfa);
137 $this->stream->writeStream($str);
138 }
139
140 return $links;
141 }
142
143 function getDirectoryPath($dir, $traversal_stack) {
144 $base_path = rtrim($dir, '/');
145 $sub_path = empty($traversal_stack) ? '' : '/' . implode('/', array_column($traversal_stack, 0));
146 return $base_path . $sub_path . '/';
147 }
148
149 function seekDirectoryHandle($directory_handle, $offset) {
150 while ($offset > 0 && ($file = @readdir($directory_handle)) !== false) {
151 if ($file === "." || $file === "..") continue;
152 $offset--;
153 }
154 }
155
156 function scanFilesDfs($dir = "/", $traversal_stack = array(), $folder_offset = 0, $limit = 0, $traversal_stack_max_size = 100,
157 $batch_size = 512, $is_recursive = true, $include_md5 = false) {
158 $links = [];
159 $batch_count = 0;
160 $batch_files = [];
161 $count = 0;
162 $traversal_stack_max_size_reached_count = 0;
163
164 $base_path = $this->getDirectoryPath($dir, $traversal_stack);
165 $directory_handle = @opendir(ABSPATH . $base_path);
166
167 $this->seekDirectoryHandle($directory_handle, $folder_offset);
168
169 while ($limit == 0 || ($limit > 0 && $count < $limit)) {
170 if (($file = @readdir($directory_handle)) !== false) {
171 if ($file === "." || $file === "..") continue;
172
173 $relative_path = $base_path . $file;
174 $absolute_path = ABSPATH . $relative_path;
175
176 $count++;
177 $folder_offset++;
178
179 $batch_files[] = $this->fileStat($relative_path, $include_md5);
180 $batch_count++;
181
182 if ($batch_count >= $batch_size) {
183 $this->stream->writeStream(serialize($batch_files));
184 $batch_count = 0;
185 $batch_files = [];
186 }
187
188 if (is_link($absolute_path)) {
189 $links[] = $relative_path;
190 } elseif ($is_recursive && is_dir($absolute_path)) {
191 if (count($traversal_stack) >= $traversal_stack_max_size) {
192 $traversal_stack_max_size_reached_count += 1;
193 continue;
194 }
195
196 closedir($directory_handle);
197
198 array_push($traversal_stack, [$file, $folder_offset]);
199 $base_path = $this->getDirectoryPath($dir, $traversal_stack);
200
201 $directory_handle = @opendir(ABSPATH . $base_path);
202 $folder_offset = 0;
203 }
204
205 continue;
206 }
207
208 if ($directory_handle !== false) {
209 closedir($directory_handle);
210 }
211
212 if (empty($traversal_stack)) {
213 break;
214 }
215 $current_info = array_pop($traversal_stack);
216
217 $base_path = $this->getDirectoryPath($dir, $traversal_stack);
218 $directory_handle = @opendir(ABSPATH . $base_path);
219
220 if ($directory_handle === false) {
221 continue;
222 }
223
224 $this->seekDirectoryHandle($directory_handle, $current_info[1]);
225 $folder_offset = $current_info[1];
226 }
227
228 if ($batch_count > 0) {
229 $this->stream->writeStream(serialize($batch_files));
230 }
231
232 return [
233 'links' => $links,
234 'traversal_stack' => $traversal_stack,
235 'folder_offset' => $folder_offset,
236 'traversal_stack_max_size_reached_count' => $traversal_stack_max_size_reached_count
237 ];
238 }
239
240 function calculateMd5($absfile, $fdata, $offset, $limit, $bsize) {
241 if ($offset == 0 && $limit == 0) {
242 $md5 = md5_file($absfile);
243 } else {
244 if ($limit == 0)
245 $limit = $fdata["size"];
246 if ($offset + $limit < $fdata["size"])
247 $limit = $fdata["size"] - $offset;
248 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
249 $handle = fopen($absfile, "rb");
250 $ctx = hash_init('md5');
251 fseek($handle, $offset, SEEK_SET);
252 $dlen = 1;
253 while (($limit > 0) && ($dlen > 0)) {
254 if ($bsize > $limit)
255 $bsize = $limit;
256 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Required for handling partial file reads with offset and limit
257 $d = fread($handle, $bsize);
258 $dlen = strlen($d);
259 hash_update($ctx, $d);
260 $limit -= $dlen;
261 }
262 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
263 fclose($handle);
264 $md5 = hash_final($ctx);
265 }
266 return $md5;
267 }
268
269 function getFilesContent($files, $withContent = true) {
270 $result = array();
271
272 foreach ($files as $file) {
273 $fdata = $this->fileStat($file);
274 $absfile = ABSPATH . $file;
275
276 if ((WPRWPFileSystem::getInstance()->isDir($absfile) === true) && !is_link($absfile)) {
277 $fdata['is_dir'] = true;
278 } else {
279 if (isset($fdata["error"]) && $fdata["error"] === "NOT_READABLE") {
280 $fdata['error'] = 'file not readable';
281 } else {
282 if ($withContent === true) {
283 $content = WPRWPFileSystem::getInstance()->getContents($absfile);
284 if ($content !== false) {
285 $fdata['content'] = $content;
286 } else {
287 $fdata['error'] = 'unable to read file';
288 }
289 }
290 }
291 }
292
293 $fs_error = WPRWPFileSystem::getInstance()->checkForErrors();
294 if (isset($fs_error)) {
295 $fdata['fs_error'] = $fs_error;
296 }
297 $result[$file] = $fdata;
298 }
299
300 return $result;
301 }
302
303 function getFilesStats($files, $offset = 0, $limit = 0, $bsize = 102400, $md5 = false) {
304 $result = array();
305 foreach ($files as $file) {
306 $fdata = $this->fileStat($file);
307 $absfile = ABSPATH.$file;
308 if (isset($fdata["error"]) && $fdata["error"] === "NOT_READABLE") {
309 $result["missingfiles"][] = $file;
310 continue;
311 }
312 if ($md5 === true && !is_dir($absfile)) {
313 $fdata["md5"] = $this->calculateMd5($absfile, $fdata, $offset, $limit, $bsize);
314 }
315 $result["stats"][] = $fdata;
316 }
317 return $result;
318 }
319
320 function uploadFiles($files, $offset = 0, $limit = 0, $bsize = 102400) {
321 $result = array();
322 foreach ($files as $file) {
323 if (WPRWPFileSystem::getInstance()->isReadable(ABSPATH.$file) === false) {
324 $result["missingfiles"][] = $file;
325 continue;
326 }
327 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Required for binary-safe chunked reading
328 $handle = fopen(ABSPATH.$file, "rb");
329 if (($handle != null) && is_resource($handle)) {
330 $fdata = $this->fileStat($file);
331 $_limit = $limit;
332 $_bsize = $bsize;
333 if ($_limit == 0)
334 $_limit = $fdata["size"];
335 if ($offset + $_limit > $fdata["size"])
336 $_limit = $fdata["size"] - $offset;
337 $fdata["limit"] = $_limit;
338 $sfdata = serialize($fdata);
339 $this->stream->writeStream($sfdata);
340 fseek($handle, $offset, SEEK_SET);
341 $dlen = 1;
342 while (($_limit > 0) && ($dlen > 0)) {
343 if ($_bsize > $_limit)
344 $_bsize = $_limit;
345 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Required for binary-safe chunked reading
346 $d = fread($handle, $_bsize);
347 $dlen = strlen($d);
348 $this->stream->writeStream($d);
349 $_limit -= $dlen;
350 }
351 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Required for cleanup
352 fclose($handle);
353 } else {
354 $result["unreadablefiles"][] = $file;
355 }
356 }
357 $result["status"] = "done";
358 return $result;
359 }
360
361 function process($request) {
362 $params = $request->params;
363 $stream_init_info = WPRStream::startStream($this->account, $request);
364
365 if (array_key_exists('stream', $stream_init_info)) {
366 $this->stream = $stream_init_info['stream'];
367 switch ($request->method) {
368 case "scanfilesglob":
369 $initdir = $params['initdir'];
370 $offset = intval($params['offset']);
371 $limit = intval($params['limit']);
372 $bsize = intval($params['bsize']);
373 $regex = $params['regex'];
374 $recurse = true;
375 if (array_key_exists('recurse', $params) && $params["recurse"] == "false") {
376 $recurse = false;
377 }
378 $resp = $this->scanFilesUsingGlob($initdir, $offset, $limit, $bsize, $recurse, $regex);
379 break;
380 case "scanfiles":
381 $links = array();
382 $dir_options = array();
383 if (array_key_exists('dir_options', $params)) {
384 $dir_options = $params['dir_options'];
385 }
386 $bsize = intval($params['bsize']);
387 foreach($dir_options as $option) {
388 $dir = $option['dir'];
389 $offset = intval($option['offset']);
390 $limit = intval($option['limit']);
391 $recurse = true;
392 if (array_key_exists('recurse', $option) && $option["recurse"] == "false") {
393 $recurse = false;
394 }
395 $md5 = true;
396 if (array_key_exists('md5', $option) && $option["md5"] == "false") {
397 $md5 = false;
398 }
399
400 $_links = $this->scanFiles($dir, $offset, $limit, $bsize, $recurse, $md5);
401 $links = array_merge($links, $_links);
402 }
403 $resp = array("status" => "done", "links" => $links);
404 break;
405 case "scanfilesdfs":
406 $resp = array();
407 $dir_options = array();
408 if (array_key_exists('dir_options', $params)) {
409 $dir_options = $params['dir_options'];
410 }
411 $bsize = intval($params['bsize']);
412 $traversal_stack_max_size = intval($params['traversal_stack_max_size']);
413 foreach($dir_options as $option) {
414 $dir = $option['dir'];
415 $traversal_stack = $option['traversal_stack'];
416 $folder_offset = intval($option['folder_offset']);
417 $limit = intval($option['limit']);
418
419 $recurse = true;
420 if (array_key_exists('recurse', $option) && $option["recurse"] == "false") {
421 $recurse = false;
422 }
423
424 $md5 = true;
425 if (array_key_exists('md5', $option) && $option["md5"] == "false") {
426 $md5 = false;
427 }
428
429 $resp[$dir] = $this->scanFilesDfs($dir, $traversal_stack, $folder_offset, $limit,
430 $traversal_stack_max_size, $bsize, $recurse, $md5);
431 }
432 $resp["status"] = "done";
433 break;
434 case "getfilesstats":
435 $files = $params['files'];
436 $offset = intval($params['offset']);
437 $limit = intval($params['limit']);
438 $bsize = intval($params['bsize']);
439 $md5 = false;
440 if (array_key_exists('md5', $params)) {
441 $md5 = true;
442 }
443 $resp = $this->getFilesStats($files, $offset, $limit, $bsize, $md5);
444 break;
445 case "sendmanyfiles":
446 $files = $params['files'];
447 $offset = intval($params['offset']);
448 $limit = intval($params['limit']);
449 $bsize = intval($params['bsize']);
450 $resp = $this->uploadFiles($files, $offset, $limit, $bsize);
451 break;
452 case "filelist":
453 $dir_options = array();
454 if (array_key_exists('dir_options', $params)) {
455 $dir_options = $params['dir_options'];
456 }
457 if (array_key_exists('chdir', $params)) {
458 chdir(ABSPATH);
459 }
460 $resp = array();
461 foreach($dir_options as $options) {
462 $glob_option = 0;
463 if (array_key_exists('onlydir', $options)) {
464 $glob_option = GLOB_ONLYDIR;
465 }
466
467 $regexes = array("*", ".*");
468 if (array_key_exists('regex', $options)) {
469 $regexes = array($options['regex']);
470 }
471
472 $md5 = false;
473 if (array_key_exists('md5', $options)) {
474 $md5 = $options['md5'];
475 }
476
477 $directoryList = array();
478
479 foreach($regexes as $regex) {
480 $directoryList = array_merge($directoryList, glob($options['dir'].$regex, $glob_option));
481 }
482 $resp[$options['dir']] = $this->getFilesStats($directoryList, 0, 0, 0, $md5);
483 }
484 break;
485 case "dirsexists":
486 $resp = array();
487 $dirs = $params['dirs'];
488
489 foreach ($dirs as $dir) {
490 $path = ABSPATH.$dir;
491 if (file_exists($path) && is_dir($path) && !is_link($path)) {
492 $resp[$dir] = true;
493 } else {
494 $resp[$dir] = false;
495 }
496 }
497
498 $resp["status"] = "Done";
499 break;
500 case "gtfilescntent":
501 $files = $params['files'];
502 $withContent = array_key_exists('withcontent', $params) ? $params['withcontent'] : true;
503 $resp = array("files_content" => $this->getFilesContent($files, $withContent));
504 break;
505 case "gtfls":
506 $resp = array();
507
508 if (array_key_exists('get_files_content', $params)) {
509 $args = $params['get_files_content'];
510 $with_content = array_key_exists('withcontent', $args) ? $args['withcontent'] : true;
511 $resp['get_files_content'] = $this->getFilesContent($args['files'], $with_content);
512 }
513
514 if (array_key_exists('get_files_stats', $params)) {
515 $args = $params['get_files_stats'];
516 $md5 = array_key_exists('md5', $args) ? $args['md5'] : false;
517 $stats = $this->getFilesStats(
518 $args['files'], $args['offset'], $args['limit'], $args['bsize'], $md5
519 );
520
521 $result = array();
522
523 if (array_key_exists('stats', $stats)) {
524 $result['stats'] = array();
525 foreach ($stats['stats'] as $stat) {
526 $result['stats'][$stat['filename']] = $stat;
527 }
528 }
529
530 if (array_key_exists('missingfiles', $stats)) {
531 $result['missingfiles'] = $stats['missingfiles'];
532 }
533
534 $resp['get_files_stats'] = $result;
535 }
536
537 break;
538 default:
539 $resp = false;
540 }
541 $end_stream_info = $this->stream->endStream();
542 if (!empty($end_stream_info) && is_array($resp)) {
543 $resp = array_merge($resp, $end_stream_info);
544 }
545 } else {
546 $resp = $stream_init_info;
547 }
548 return $resp;
549 }
550 }
551 endif;