PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.6.0
Defender Security – Malware Scanner, Login Security & Firewall v5.6.0
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / traits / io.php

io.php in Defender Security – Malware Scanner, Login Security & Firewall 5.6.0, at src/traits/io.php

362 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Helper functions for Input Output related tasks.
4 *
5 * @package WP_Defender\Traits
6 */
7
8 namespace WP_Defender\Traits;
9
10 use RecursiveIteratorIterator;
11 use RecursiveDirectoryIterator;
12 use WP_Defender\Helper\File as File_Helper;
13 use WP_Defender\Component\Logger\Rotation_Logger as Logger;
14 use WP_Filesystem_Base;
15
16 trait IO {
17
18 /**
19 * A simple function to create & return the folder that we can use to write tmp files.
20 *
21 * @param bool $main_site_path If true then return main site's upload dir path for a multisite.
22 *
23 * @since 4.1.0 The `$main_site_path` parameter was added.
24 * @return string
25 */
26 protected function get_tmp_path( bool $main_site_path = false ): string {
27 global $wp_filesystem;
28 // Initialize the WP filesystem, no more using 'file-put-contents' function.
29 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
30 require_once ABSPATH . '/wp-admin/includes/file.php';
31 WP_Filesystem();
32 }
33 $is_switch_to_main_site = $main_site_path && is_multisite() && ! is_main_site();
34 if ( $is_switch_to_main_site ) {
35 // Switch to the main site.
36 switch_to_blog( get_main_site_id() );
37 }
38
39 $upload_dir = wp_upload_dir()['basedir'];
40
41 if ( $is_switch_to_main_site ) {
42 // Restore the current site if switched.
43 restore_current_blog();
44 }
45
46 $tmp_dir = $upload_dir . DIRECTORY_SEPARATOR . 'wp-defender';
47 if ( ! is_dir( $tmp_dir ) ) {
48 wp_mkdir_p( $tmp_dir );
49 }
50
51 if ( ! is_file( $tmp_dir . DIRECTORY_SEPARATOR . 'index.php' ) ) {
52 $wp_filesystem->put_contents( $tmp_dir . DIRECTORY_SEPARATOR . 'index.php', '' );
53 }
54
55 $file_helper = wd_di()->get( File_Helper::class );
56 $file_helper->maybe_dir_access_deny( $tmp_dir );
57
58 return $tmp_dir;
59 }
60
61 /**
62 * Returns the path to the log file for a given category.
63 *
64 * @param string $category The category of the log file. Defaults to an empty string.
65 *
66 * @return string The path to the log file.
67 */
68 public function get_log_path( $category = '' ): string {
69 $file = '' === $category ? wd_internal_log() : $category;
70
71 $logger = new Logger();
72 $file_name = $logger->generate_file_name( $file );
73
74 return $this->get_tmp_path() . DIRECTORY_SEPARATOR . $file_name;
75 }
76
77 /**
78 * Create a lock. This will be used for 2FA.
79 *
80 * @return string
81 */
82 protected function get_2fa_lock_path(): string {
83 return $this->get_tmp_path() . DIRECTORY_SEPARATOR . 'two-fa.lock';
84 }
85
86 /**
87 * Delete a folder with every content inside.
88 *
89 * @param string $dir The path to the folder.
90 *
91 * @return bool
92 */
93 public function delete_dir( $dir ): bool {
94 global $wp_filesystem;
95 // Initialize the WP filesystem, no more using 'file-put-contents' function.
96 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
97 require_once ABSPATH . '/wp-admin/includes/file.php';
98 WP_Filesystem();
99 }
100 if ( ! is_dir( $dir ) ) {
101 return false;
102 }
103 $it = new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS );
104 $files = new RecursiveIteratorIterator(
105 $it,
106 RecursiveIteratorIterator::CHILD_FIRST
107 );
108 $ret = true;
109 foreach ( $files as $file ) {
110 if ( $file->isDir() ) {
111 $ret = $wp_filesystem->rmdir( $file->getPathname(), true );
112 } else {
113 $wp_filesystem->delete( $file->getPathname() );
114 }
115 if ( false === $ret ) {
116 return false;
117 }
118 }
119
120 return $wp_filesystem->rmdir( $dir, true );
121 }
122
123 /**
124 * Not remove double quotes inside str_replace().
125 *
126 * @param string $data The string or array being searched and replaced on.
127 *
128 * @return array|string
129 */
130 protected function convert_end_lines_dos_to_linux( $data ) {
131 return str_replace( array( "\r\n", "\r" ), "\n", $data );
132 }
133
134 /**
135 * Not remove double quotes inside str_replace().
136 *
137 * @param string $data The string or array being searched and replaced on.
138 *
139 * @return array|string
140 */
141 protected function convert_end_lines_linux_to_dos( $data ) {
142 return str_replace( "\n", "\r\n", $this->convert_end_lines_dos_to_linux( $data ) );
143 }
144
145 /**
146 * Compare hashes on different OS.
147 *
148 * @param string $file_path The filename.
149 * @param string|array $file_hash The user-supplied string to compare against.
150 *
151 * @return bool
152 */
153 protected function compare_hashes_on_different_os( $file_path, $file_hash ) {
154 if ( hash_equals( md5_file( $file_path ), $file_hash ) ) {
155 return true;
156 }
157 if ( hash_equals( $this->hash_file( $file_path, 'linux' ), $file_hash ) ) {
158 return true;
159 }
160 if ( hash_equals( $this->hash_file( $file_path, 'dos' ), $file_hash ) ) {
161 return true;
162 }
163
164 return false;
165 }
166
167 /**
168 * Compare hashes.
169 *
170 * @param string $file_path Path to file.
171 * @param string|array $file_hash Hash or some hashes of file2, e.g. for readme.txt.
172 *
173 * @return bool
174 */
175 public function compare_hashes( $file_path, $file_hash ) {
176 if ( is_string( $file_hash ) ) {
177 return $this->compare_hashes_on_different_os( $file_path, $file_hash );
178 } elseif ( is_array( $file_hash ) ) {
179 // Sometimes file has some hashes.
180 foreach ( $file_hash as $hash_value ) {
181 if ( $this->compare_hashes_on_different_os( $file_path, $hash_value ) ) {
182 return true;
183 }
184 }
185
186 return false;
187 } else {
188 return false;
189 }
190 }
191
192 /**
193 * Hash a file in chunks.
194 *
195 * @param string $file_path Path to a file.
196 * @param string $convert_to Convert end of lines characters to linux or dos.
197 *
198 * @return bool|string
199 * @since 3.10.0
200 */
201 protected function hash_file( string $file_path, string $convert_to = '' ) {
202 global $wp_filesystem;
203 // Initialize the WP filesystem, no more using 'file-put-contents' function.
204 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
205 require_once ABSPATH . '/wp-admin/includes/file.php';
206 WP_Filesystem();
207 }
208 if ( ! file_exists( $file_path ) ) {
209 return false;
210 }
211
212 $context = hash_init( 'md5' );
213 $data = $wp_filesystem->get_contents( $file_path );
214
215 if ( 'linux' === $convert_to ) {
216 $data = $this->convert_end_lines_dos_to_linux( $data );
217 } elseif ( 'dos' === $convert_to ) {
218 $data = $this->convert_end_lines_linux_to_dos( $data );
219 }
220
221 hash_update( $context, $data );
222
223 return hash_final( $context, false );
224 }
225
226 /**
227 * Retrieves the lock file path used in scanning.
228 *
229 * @return string The lock file path.
230 *
231 * @throws \RuntimeException If the lock file name is not defined.
232 */
233 protected function get_lock_path(): string {
234 if ( '' === $this->lock_filename ) {
235 throw new \RuntimeException( 'Lock file name must be defined in the class using IO trait.' );
236 }
237
238 return $this->get_tmp_path() . DIRECTORY_SEPARATOR . $this->lock_filename;
239 }
240
241 /**
242 * Create a file lock, so we can check if a process already running.
243 */
244 public function create_lock() {
245 $this->remove_lock();
246 file_put_contents( $this->get_lock_path(), time(), LOCK_EX ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
247 }
248
249 /**
250 * Delete file lock.
251 */
252 public function remove_lock() {
253 if ( file_exists( $this->get_lock_path() ) ) {
254 wp_delete_file( $this->get_lock_path() );
255 }
256 }
257
258 /**
259 * Check if a lock is valid.
260 *
261 * @return bool
262 */
263 public function has_lock(): bool {
264 global $wp_filesystem;
265 // Initialize the WP filesystem, no more using 'file-put-contents' function.
266 if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
267 require_once ABSPATH . '/wp-admin/includes/file.php';
268 WP_Filesystem();
269 }
270 if ( ! file_exists( $this->get_lock_path() ) ) {
271 return false;
272 }
273 $time = $wp_filesystem->get_contents( $this->get_lock_path() );
274 if ( strtotime( '+90 seconds', $time ) < time() ) {
275 // Usually a timeout window is 30 seconds, so we should allow lock at 1.30min for safe.
276 return false;
277 }
278
279 return true;
280 }
281
282 /**
283 * Acquire a lock for a given cron event.
284 *
285 * @param string $event Unique cron event name.
286 * @param string $ttl Cron schedule name to hold the lock. Default is 'every_minute'.
287 * @return bool True if lock is acquired, False if another process is running.
288 */
289 public function acquire_cron_lock( string $event, string $ttl = 'every_minute' ): bool {
290 $lock_key = "{$event}_lock";
291 $last_run_key = "{$event}_last_run";
292 $now = time();
293 $lock_duration = 59; // Default lock duration.
294
295 // Set lock duration based on cron schedule.
296 switch ( $ttl ) {
297 case 'twicedaily':
298 $lock_duration = ( DAY_IN_SECONDS / 2 ) - 1;
299 break;
300 case 'hourly':
301 $lock_duration = HOUR_IN_SECONDS - 1;
302 break;
303 case 'daily':
304 $lock_duration = DAY_IN_SECONDS - 1;
305 break;
306 default:
307 break;
308 }
309
310 // Prevent execution if it has already run within lock duration.
311 $last_run = get_site_option( $last_run_key, 0 );
312 if ( $now < $last_run + $lock_duration ) {
313 return false;
314 }
315
316 // Check if another process is running.
317 $lock_time = (int) get_site_option( $lock_key, 0 );
318 if ( $lock_time > 0 && ( $now < $lock_time + $lock_duration ) ) {
319 return false;
320 }
321
322 // Acquire the lock.
323 if ( ! update_site_option( $lock_key, $now ) ) {
324 return false;
325 }
326 return true;
327 }
328
329 /**
330 * Release the lock and update the last run time.
331 *
332 * @param string $event Unique cron event name.
333 */
334 public function release_cron_lock( string $event ): void {
335 $lock_key = "{$event}_lock";
336 $last_run_key = "{$event}_last_run";
337 update_site_option( $last_run_key, time() );
338 delete_site_option( $lock_key );
339 }
340
341 /**
342 * Detect the line ending style used in a given text.
343 *
344 * @param string $text The text to analyze.
345 *
346 * @return string The detected line ending style: "\r\n" for Windows, "\n" for Unix, or "\r" for Classic Mac.
347 */
348 public function detect_line_ending( string $text ): string {
349 $count_crlf = substr_count( $text, "\r\n" );
350 $count_lf = substr_count( $text, "\n" ) - $count_crlf;
351 $count_cr = substr_count( $text, "\r" ) - $count_crlf;
352
353 if ( $count_crlf >= $count_lf && $count_crlf >= $count_cr ) {
354 return "\r\n"; // Windows-style.
355 } elseif ( $count_lf >= $count_cr ) {
356 return "\n"; // Unix-style.
357 } else {
358 return "\r"; // Classic Mac (rare).
359 }
360 }
361 }
362