GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Issues (217)

Security Analysis    no vulnerabilities found

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

classes/class-path.php (14 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace HM\BackUpWordPress;
4
5
/**
6
 * Manages both the backup path and site root
7
 *
8
 * Handles calculating & protecting the directory that backups will be stored in
9
 * as well as the directory that is being backed up
10
 */
11
class Path {
12
13
	/**
14
	 * The path to the directory that backup files are stored in
15
	 *
16
	 * @var string $this->path
17
	 */
18
	private $path;
19
20
	/**
21
	 * The path to the directory that will be backed up
22
	 *
23
	 * @var string $this->root
24
	 */
25
	private $root;
26
27
	/**
28
	 * The path to the directory that backup files are stored in
29
	 *
30
	 * @var string $this->path
31
	 */
32
	private $custom_path;
33
34
	/**
35
	 * Contains the instantiated Path instance
36
	 *
37
	 * @var Path $this->instance
38
	 */
39
	private static $instance;
40
41
	/**
42
	 * Protected constructor to prevent creating a new instance of the
43
	 * *Singleton* via the `new` operator from outside of this class.
44
	 */
45
	protected function __construct() {}
46
47
	/**
48
	 * Private clone method to prevent cloning of the instance of the
49
	 * *Singleton* instance.
50
	 */
51
	private function __clone() {}
52
53
	/**
54
	 * Private unserialize method to prevent unserializing of the *Singleton*
55
	 * instance.
56
	 */
57
	private function __wakeup() {}
58
59
	/**
60
	 * Returns the *Singleton* instance of this class.
61
	 *
62
	 * @staticvar Path $instance The *Singleton* instances of this class.
63
	 *
64
	 * @return Path The *Singleton* instance.
65
	 */
66
	public static function get_instance() {
67
68
		if ( ! ( self::$instance instanceof Path ) ) {
69
			self::$instance = new Path();
70
		}
71
72
		return self::$instance;
73
	}
74
75
	/**
76
	 * Convenience method for quickly grabbing the path
77
	 */
78
	public static function get_path() {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
79
		return self::get_instance()->get_calculated_path();
80
	}
81
82
	/**
83
	 * Convenience method for quickly grabbing the root
84
	 */
85
	public static function get_root() {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
86
		return self::get_instance()->get_calculated_root();
87
	}
88
89
	/**
90
	 * Calculate the path to the site "home" directory.
91
	 *
92
	 * The home directory is the path equivalent to the home_url. That is,
93
	 * the path to the true root of the website. In situations where WordPress is
94
	 * installed in a subdirectory the home path is different to ABSPATH
95
	 *
96
	 * @param string $site_path The site_path to use when calculating the home path, defaults to ABSPATH
97
	 */
98
	public static function get_home_path( $site_path = ABSPATH ) {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
99
100
		if ( defined( 'HMBKP_ROOT' ) && HMBKP_ROOT ) {
101
			return wp_normalize_path( HMBKP_ROOT );
102
		}
103
104
		$home_path = wp_normalize_path( $site_path );
105
106
		if ( path_in_php_open_basedir( dirname( $site_path ) ) ) {
107
108
			$home    = set_url_scheme( get_option( 'home' ), 'http' );
109
			$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
110
			if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) {
111
				$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */
112
				$pos = strripos( wp_normalize_path( $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) );
113
				$home_path = substr( wp_normalize_path( $_SERVER['SCRIPT_FILENAME'] ), 0, $pos );
114
				$home_path = trailingslashit( $home_path );
115
			}
116
117
			if ( is_multisite() ) {
118
				$slashed_home      = trailingslashit( get_option( 'home' ) );
119
				$base              = parse_url( $slashed_home, PHP_URL_PATH );
120
				$document_root_fix = wp_normalize_path( realpath( $_SERVER['DOCUMENT_ROOT'] ) );
121
				$abspath_fix       = wp_normalize_path( ABSPATH );
122
				$home_path         = strpos( $abspath_fix, $document_root_fix ) === 0 ? $document_root_fix . $base : $home_path;
123
			}
124
		}
125
126
		return wp_normalize_path( untrailingslashit( $home_path ) );
127
128
	}
129
130
	/**
131
	 * get the calculated path to the directory where backups will be stored
132
	 */
133
	private function get_calculated_path() {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
134
135
		// Calculate the path if needed
136
		if ( empty( $this->path ) || ! wp_is_writable( $this->path ) ) {
137
			$this->calculate_path();
138
		}
139
140
		// Ensure the backup directory is protected
141
		$this->protect_path();
142
143
		return wp_normalize_path( $this->path );
144
145
	}
146
147
	/**
148
	 * Set the path directly, overriding the default
149
	 *
150
	 * @param $path
151
	 */
152
	public function set_path( $path ) {
153
154
		$this->custom_path = $path;
155
156
		// Re-calculate the backup path
157
		$this->calculate_path();
158
159
	}
160
161
	/**
162
	 * get the calculated path to the directory that will be backed up
163
	 */
164
	private function get_calculated_root() {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
165
166
		$root = self::get_home_path();
167
168
		if ( defined( 'HMBKP_ROOT' ) && HMBKP_ROOT ) {
169
			$root = HMBKP_ROOT;
170
		}
171
172
		if ( $this->root ) {
173
			$root = $this->root;
174
		}
175
176
		return wp_normalize_path( $root );
177
178
	}
179
180
	/**
181
	 * Set the root path directly, overriding the default
182
	 *
183
	 * @param $root
184
	 */
185
	public function set_root( $root ) {
186
		$this->root = $root;
187
	}
188
189
	public function reset_path() {
190
		$this->path = $this->custom_path = '';
191
	}
192
193
	/**
194
	 * Get the path to the default backup location in wp-content
195
	 */
196
	public function get_default_path() {
197
		return trailingslashit( wp_normalize_path( WP_CONTENT_DIR ) ) . 'backupwordpress-' . substr( HMBKP_SECURE_KEY, 0, 10 ) . '-backups';
198
	}
199
200
	/**
201
	 * Get the path to the fallback backup location in uploads
202
	 */
203
	public function get_fallback_path() {
204
205
		$upload_dir = wp_upload_dir();
206
207
		return trailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . 'backupwordpress-' . substr( HMBKP_SECURE_KEY, 0, 10 ) . '-backups';
208
209
	}
210
211
	/**
212
	 * Get the path to the custom backup location if it's been set
213
	 */
214
	public function get_custom_path() {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
215
216
		if ( $this->custom_path ) {
217
			return $this->custom_path;
218
		}
219
220
		if ( defined( 'HMBKP_PATH' ) && wp_is_writable( HMBKP_PATH ) ) {
221
			return HMBKP_PATH;
222
		}
223
224
		return '';
225
226
	}
227
228
	/**
229
	 * Builds an array containing existing backups folders.
230
	 *
231
	 * @return array
232
	 */
233
	public function get_existing_paths() {
234
235
		if ( false === $default = glob( WP_CONTENT_DIR . '/backupwordpress-*-backups', GLOB_ONLYDIR ) ) {
236
			$default = array();
237
		}
238
239
		$upload_dir = wp_upload_dir();
240
241
		if ( false === $fallback = glob( $upload_dir['basedir'] . '/backupwordpress-*-backups', GLOB_ONLYDIR ) ) {
242
			$fallback = array();
243
		}
244
245
		$paths = array_merge( $default, $fallback );
246
		$paths = array_map( 'wp_normalize_path', $paths );
247
248
		return $paths;
249
250
	}
251
252
	/**
253
	 * Returns the first existing path if there is one
254
	 *
255
	 * @return string Backup path if found empty string if not
256
	 */
257
	public function get_existing_path() {
258
259
		$paths = $this->get_existing_paths();
260
261
		if ( ! empty( $paths[0] ) ) {
262
			return $paths[0];
263
		}
264
265
		return '';
266
267
	}
268
269
	/**
270
	 * Calculate the backup path and create the directory if it doesn't exist.
271
	 *
272
	 * Tries all possible locations and uses the first one possible
273
	 *
274
	 * @return
275
	 */
276
	public function calculate_path() {
277
278
		$paths = array();
279
280
		// If we have a custom path then try to use it
281
		if ( $this->get_custom_path() ) {
282
			$paths[] = $this->get_custom_path();
283
		}
284
285
		// If there is already a backups directory then try to use that
286
		if ( $this->get_existing_path() ) {
287
			$paths[] = $this->get_existing_path();
288
		}
289
290
		// If not then default to a new directory in wp-content
291
		$paths[] = $this->get_default_path();
292
293
		// If that didn't work then fallback to a new directory in uploads
294
		$paths[] = $this->get_fallback_path();
295
296
		// Loop through possible paths, use the first one that exists/can be created and is writable
297
		foreach ( $paths as $path ) {
298
			if ( wp_mkdir_p( $path ) && wp_is_writable( $path ) ) { // Also handles fixing perms / directory already exists
299
				break;
300
			}
301
		}
302
303
		if ( file_exists( $path ) && wp_is_writable( $path ) ) {
304
			$this->path = $path;
0 ignored issues
show
The variable $path seems to be defined by a foreach iteration on line 297. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
305
		}
306
307
	}
308
309
	/**
310
	 * Protect the directory that backups are stored in
311
	 *
312
	 * - Adds an index.html file in an attempt to disable directory browsing
313
	 * - Adds a .httaccess file to deny direct access if on Apache
314
	 *
315
	 * @param string $reset
316
	 */
317
	public function protect_path( $reset = 'no' ) {
318
319
		global $is_apache;
320
321
		// Protect against directory browsing by including an index.html file
322
		$index = $this->path . '/index.html';
323
324
		if ( 'reset' === $reset && file_exists( $index ) ) {
325
			@unlink( $index );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
326
		}
327
328
		if ( ! file_exists( $index ) && wp_is_writable( $this->path ) ) {
329
			file_put_contents( $index, '' );
330
		}
331
332
		$htaccess = $this->path . '/.htaccess';
333
334
		if ( ( 'reset' === $reset ) && file_exists( $htaccess ) ) {
335
			@unlink( $htaccess );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
336
		}
337
338
		// Protect the directory with a .htaccess file on Apache servers
339
		if ( $is_apache && function_exists( 'insert_with_markers' ) && ! file_exists( $htaccess ) && wp_is_writable( $this->path ) ) {
340
341
			$contents[] = '# ' . sprintf( __( 'This %s file ensures that other people cannot download your backup files.', 'backupwordpress' ), '.htaccess' );
0 ignored issues
show
Coding Style Comprehensibility introduced by
$contents was never initialized. Although not strictly required by PHP, it is generally a good practice to add $contents = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
342
			$contents[] = '';
343
			$contents[] = '<IfModule mod_rewrite.c>';
344
			$contents[] = 'RewriteEngine On';
345
			$contents[] = 'RewriteCond %{QUERY_STRING} !key=' . HMBKP_SECURE_KEY;
346
			$contents[] = 'RewriteRule (.*) - [F]';
347
			$contents[] = '</IfModule>';
348
			$contents[] = '';
349
350
			file_put_contents( $htaccess, '' );
351
352
			insert_with_markers( $htaccess, 'BackUpWordPress', $contents );
353
354
		}
355
356
	}
357
358
	/**
359
	 * If we have more than one path then move any existing backups to the current path and remove them
360
	 */
361
	public function merge_existing_paths() {
362
363
		$paths = $this->get_existing_paths();
364
365
		if ( ( $paths && $this->get_custom_path() ) || count( $paths ) > 1 ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $paths of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
366
			foreach ( $paths as $old_path ) {
367
				$this->move_old_backups( $old_path );
368
			}
369
		}
370
371
	}
372
373
	/**
374
	 * Move backup files from an existing directory and the new
375
	 * location
376
	 *
377
	 * @param string $path 	The path to move the backups from
0 ignored issues
show
There is no parameter named $path. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
378
	 */
379
	public function move_old_backups( $from ) {
380
381
		if ( ! is_readable( $from ) ) {
382
			return;
383
		}
384
385
		if ( ! wp_is_writable( Path::get_path() ) ) {
386
			return;
387
		}
388
389
		// Move any existing backups
390
		if ( $handle = opendir( $from ) ) {
391
392
			// Loop through the backup directory
393
			while ( false !== ( $file = readdir( $handle ) ) ) {
394
395
				// Find all zips
396
				if ( 'zip' === pathinfo( $file, PATHINFO_EXTENSION ) ) {
397
398
					// Try to move them
399
					if ( ! @rename( trailingslashit( $from ) . $file, trailingslashit( Path::get_path() ) . $file ) ) {
400
401
						// If we can't move them then try to copy them
402
						copy( trailingslashit( $from ) . $file, trailingslashit( Path::get_path() ) . $file );
403
404
					}
405
				}
406
			}
407
408
			closedir( $handle );
409
410
		}
411
412
		// Delete the old directory if it's inside WP_CONTENT_DIR
413
		if ( false !== strpos( $from, WP_CONTENT_DIR ) &&  Path::get_path() !== $from ) {
414
			rmdirtree( $from );
415
		}
416
417
	}
418
419
	/**
420
	 * Clean any temporary / incomplete backups from the backups directory
421
	 */
422
	public function cleanup() {
423
424
		// Don't cleanup a custom path, who knows what other stuff is there
425
		if ( Path::get_path() === $this->get_custom_path() ) {
426
			return;
427
		}
428
429
		foreach ( new CleanUpIterator( new \DirectoryIterator( Path::get_path() ) ) as $file ) {
430
431
			if ( $file->isDot() || ! $file->isReadable() || ! $file->isFile() ) {
432
				continue;
433
			}
434
435
			@unlink( $file->getPathname() );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
436
437
		}
438
	}
439
}
440
441
class CleanUpIterator extends \FilterIterator {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
442
443
	// Don't match index.html, files with zip extension or status logfiles.
444
	public function accept() {
445
446
		// Don't remove existing backups
447
		if ( 'zip' === pathinfo( $this->current()->getFilename(), PATHINFO_EXTENSION ) ) {
448
			return false;
449
		}
450
451
		// Don't remove the index.html file
452
		if ( 'index.html' === $this->current()->getBasename() ) {
453
			return false;
454
		}
455
456
		// Don't remove the file manifest
457
		if ( '.files' === $this->current()->getBasename() ) {
458
			return false;
459
		}
460
461
		// Don't cleanup the backup running file
462
		return ! preg_match( '/(.*-running)/', $this->current() );
463
464
	}
465
}
466