VaultPress_Filesystem::exec_checksum()   A
last analyzed

Complexity

Conditions 6
Paths 17

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 17
nop 2
dl 0
loc 16
rs 9.1111
c 0
b 0
f 0
1
<?php
2
// don't call the file directly
3
defined( 'ABSPATH' ) or die();
4
5
class VaultPress_Filesystem {
6
7
	var $type = null;
8
	var $dir  = null;
9
	var $keys = array( 'ino', 'uid', 'gid', 'size', 'mtime', 'blksize', 'blocks' );
10
11
	function __construct() {
12
	}
13
14
	function want( $type ) {
15
		$vp = VaultPress::init();
16
17
		if ( $type == 'plugins' ) {
18
			$this->dir = realpath( $vp->resolve_content_dir() . 'plugins' );
19
			$this->type = 'p';
20
			return true;
21
		}
22
		if ( $type == 'themes' ) {
23
			$this->dir = realpath( $vp->resolve_content_dir() . 'themes' );
24
			$this->type = 't';
25
			return true;
26
		}
27
		if ( $type == 'uploads' ) {
28
			$this->dir = realpath( $vp->resolve_upload_path() );
29
			$this->type = 'u';
30
			return true;
31
		}
32
		if ( $type == 'content' ) {
33
			$this->dir = realpath( $vp->resolve_content_dir() );
34
			$this->type = 'c';
35
			return true;
36
		}
37
		if ( $type == 'root' ) {
38
			$this->dir = realpath( ABSPATH );
39
			$this->type = 'r';
40
			return true;
41
		}
42
		die( 'naughty naughty' );
43
	}
44
45
	function fdump( $file ) {
46
		header("Content-Type: application/octet-stream;");
47
		header("Content-Transfer-Encoding: binary");
48
		@ob_end_clean();
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...
49
		if ( !file_exists( $file ) || !is_readable( $file ) ) {
50
			$file_name = basename( $file );
51
			if ( 'wp-config.php' == $file_name ) {
52
				$dir = dirname( $file );
53
				$dir = explode( DIRECTORY_SEPARATOR, $dir );
54
				array_pop( $dir );
55
				$dir = implode( DIRECTORY_SEPARATOR, $dir );
56
				$file = trailingslashit( $dir ) . $file_name;
57
				if ( !file_exists( $file ) || !is_readable( $file ) )
58
					die( "no such file" );
59
			} else {
60
				die( "no such file" );
61
			}
62
		}
63
		if ( !is_file( $file ) && !is_link( $file ) )
64
			die( "can only dump files" );
65
		$fp = @fopen( $file, 'rb' );
66
		if ( !$fp )
67
			die( "could not open file" );
68
		while ( !feof( $fp ) )
69
			echo @fread( $fp, 8192 );
70
		@fclose( $fp );
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...
71
		die();
72
	}
73
74
	function exec_checksum( $file, $method ) {
75
		if ( !function_exists( 'exec' ) )
76
			return false;
77
		$out = array();
78
		if ( 'md5' == $method )
79
			$method_bin = 'md5sum';
80
		if ( 'sha1' == $method )
81
			$method_bin = 'sha1sum';
82
		$checksum = '';
83
		exec( sprintf( '%s %s', escapeshellcmd( $method_bin ), escapeshellarg( $file ) ), $out );
0 ignored issues
show
Bug introduced by
The variable $method_bin does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
84
		if ( !empty( $out ) )
85
			$checksum = trim( array_shift( explode( ' ', array_pop( $out ) ) ) );
0 ignored issues
show
Bug introduced by
explode(' ', array_pop($out)) cannot be passed to array_shift() as the parameter $array expects a reference.
Loading history...
86
		if ( !empty( $checksum ) )
87
			return $checksum;
88
		return false;
89
	}
90
91
	function checksum_file( $file, $method ) {
92
		$use_exec = false;
93
		if ( filesize( $file ) >= 104857600 )
94
			$use_exec = true;
95
		switch( $method ) {
96 View Code Duplication
			case 'md5':
97
			if ( $use_exec ) {
98
				$checksum = $this->exec_checksum( $file, $method );
99
				if ( !empty( $checksum ) )
100
					return $checksum;
101
			}
102
			return md5_file( $file );
103
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
104 View Code Duplication
			case 'sha1':
105
			if ( $use_exec ) {
106
				$checksum = $this->exec_checksum( $file, $method );
107
				if ( !empty( $checksum ) )
108
					return $checksum;
109
			}
110
			return sha1_file( $file );
111
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
112
			default:
113
			return false;
114
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
115
		}
116
	}
117
118
	function stat( $file, $md5=true, $sha1=true ) {
119
		if ( ! file_exists( $file ) )
120
			return false;
121
		
122
		$rval = array();
123
		foreach ( stat( $file ) as $i => $v ) {
124
			if ( is_numeric( $i ) )
125
				continue;
126
			$rval[$i] = $v;
127
		}
128
		$rval['type'] = filetype( $file );
129
		if ( $rval['type'] == 'file' ) {
130
			if ( $md5 )
131
				$rval['md5'] = $this->checksum_file( $file, 'md5' );
132
			if ( $sha1 )
133
				$rval['sha1'] = $this->checksum_file( $file, 'sha1' );
134
		}
135
		$dir = $this->dir;
136
		if ( 0 !== strpos( $file, $dir ) && 'wp-config.php' == basename( $file ) ) {
137
			$dir = explode( DIRECTORY_SEPARATOR, $dir );
138
			array_pop( $dir );
139
			$dir = implode( DIRECTORY_SEPARATOR, $dir );
140
		}
141
		$rval['full_path'] = realpath( $file );
142
		
143
		//	Avoid rebuilding path tidy-up regex when fetching multiple entries
144
		static $last_dir = null;
145
		static $dir_regex = null;
146
		if ( $last_dir !== $dir ) {
147
			$dir_regex = '#' . preg_quote( $dir ) . '#';
148
			$last_dir = $dir;
149
		}
150
		
151
		$rval['path'] = preg_replace( $dir_regex, '', $file, 1 );
152
		return $rval;
153
	}
154
155
	function ls( $what, $md5=false, $sha1=false, $limit=null, $offset=null, $full_list=false ) {
156
		clearstatcache();
157
		$path = realpath($this->dir . $what);
158
		$dir = $this->dir;
159 View Code Duplication
		if ( !$path && '/wp-config.php' == $what ) {
160
			$dir = explode( DIRECTORY_SEPARATOR, $dir );
161
			array_pop( $dir );
162
			$dir = implode( DIRECTORY_SEPARATOR, $dir );
163
			$path = realpath( $dir . $what );
164
		}
165
		if ( is_file($path) )
166
			return $this->stat( $path, $md5, $sha1 );
167
		if ( is_dir($path) ) {
168
			$entries = array();
169
			$current = 0;
170
			$offset = (int)$offset;
171
			$orig_limit = (int)$limit;
172
			$limit = $offset + (int)$limit;
173
			foreach ( (array)$this->scan_dir( $path ) as $i ) {
174
				if ( !$full_list && !$this->should_backup_file( $i ) )
175
					continue;
176
				$current++;
177
				if ( $offset >= $current )
178
					continue;
179
				if ( $limit && $limit < $current )
180
					break;
181
182
				// don't sha1 files over 100MB if we are batching due to memory consumption
183
				if ( $sha1 && $orig_limit > 1 && is_file( $i ) && (int)@filesize( $i ) > 104857600 )
184
					$sha1 = false;
185
186
				$entries[] = $this->stat( $i, $md5, $sha1 );
187
			}
188
			return $entries;
189
		}
190
	}
191
192
	function should_backup_file( $filepath ) {
193
		$vp = VaultPress::init();
194
		if ( is_dir( $filepath ) )
195
			$filepath = trailingslashit( $filepath );
196
		$regex_patterns = $vp->get_should_ignore_files();
197
		foreach ( $regex_patterns as $pattern ) {
198
			$matches = array();
199
			if ( preg_match( $pattern, $filepath, $matches ) ) {
200
				return false;
201
			}
202
		}
203
		return true;
204
	}
205
206
	function validate( $file ) {
207
		$rpath = realpath( $this->dir.$file );
208
		$dir = $this->dir;
209 View Code Duplication
		if ( !$rpath && '/wp-config.php' == $file ) {
210
			$dir = explode( DIRECTORY_SEPARATOR, $dir );
211
			array_pop( $dir );
212
			$dir = implode( DIRECTORY_SEPARATOR, $dir );
213
			$rpath = realpath( $dir . $file );
214
		}
215
		if ( !$rpath )
216
			die( serialize( array( 'type' => 'null', 'path' => $file ) ) );
217
		if ( is_dir( $rpath ) )
218
			$rpath = "$rpath/";
219
		if ( strpos( $rpath, $dir ) !== 0 )
220
			return false;
221
		return true;
222
	}
223
224
	function dir_examine( $subdir='', $recursive=true, $origin=false ) {
225
		$res = array();
226
		if ( !$subdir )
227
			$subdir='/';
228
		$dir = $this->dir . $subdir;
229
		if ( $origin === false )
230
			$origin = $this->dir . $subdir;
231
		if ( is_file($dir) ) {
232
			if ( $origin ==  $dir )
233
				$name = str_replace( $this->dir, '/', $subdir );
234
			else
235
				$name = str_replace( $origin, '/', $dir );
236
			$res[$name] = $this->stat( $dir.$entry );
0 ignored issues
show
Bug introduced by
The variable $entry seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
237
			return $res;
238
		}
239
		$d = dir( $dir );
240
		if ( !$d )
241
			return $res;
242
		while ( false !== ( $entry = $d->read() ) ) {
243
			$rpath = realpath( $dir.$entry );
244
			$bname = basename( $rpath );
0 ignored issues
show
Unused Code introduced by
$bname is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
245
			if ( is_link( $dir.$entry ) )
246
				continue;
247
			if ( $entry == '.' || $entry == '..' || $entry == '...' )
248
				continue;
249
			if ( !$this->validate( $subdir.$entry ) )
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->validate($subdir . $entry) of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
250
				continue;
251
			$name = str_replace( $origin, '/', $dir.$entry );
252
			$res[$name] = $this->stat( $dir.$entry );
253
			if ( $recursive && is_dir( $this->dir.$subdir.'/'.$entry ) ) {
254
				$res = array_merge( $res, $this->dir_examine( $subdir.$entry.'/', $recursive, $origin ) );
0 ignored issues
show
Bug introduced by
It seems like $origin defined by $this->dir . $subdir on line 230 can also be of type string; however, VaultPress_Filesystem::dir_examine() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
255
			}
256
		}
257
		return $res;
258
	}
259
260
	function dir_checksum( $base, &$list, $recursive=true ) {
261
		if ( $list == null )
262
			$list = array();
263
264
		if ( 0 !== strpos( $base, $this->dir ) )
265
			$base = $this->dir . rtrim( $base, '/' );
266
267
		$shortbase = substr( $base, strlen( $this->dir ) );
268
		if ( !$shortbase )
269
			$shortbase = '/';
270
		$stat = stat( $base );
0 ignored issues
show
Unused Code introduced by
$stat is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
271
		$directories = array();
272
		$files = (array)$this->scan_dir( $base );
273
		array_push( $files, $base );
274
		foreach ( $files as $file ) {
275
			if ( $file !== $base && @is_dir( $file ) ) {
276
				$directories[] = $file;
277
				continue;
278
			}
279
			$stat = @stat( $file );
280
			if ( !$stat )
281
				continue;
282
			$shortstat = array();
283
			foreach( $this->keys as $key ) {
284
				if ( isset( $stat[$key] ) )
285
					$shortstat[$key] = $stat[$key];
286
			}
287
			$list[$shortbase][basename( $file )] = $shortstat;
288
		}
289
		$list[$shortbase] = md5( serialize( $list[$shortbase] ) );
290
		if ( !$recursive )
291
			return $list;
292
		foreach ( $directories as $dir ) {
293
			$this->dir_checksum( $dir, $list, $recursive );
294
		}
295
		return $list;
296
	}
297
298
	function scan_dir( $path ) {
299
		$files = array();
300
301
		if ( false === is_readable( $path ) ) {
302
			return array();
303
		}
304
305
		$dh = opendir( $path );
306
307
		if ( false === $dh ) {
308
			return array();
309
		}
310
311
		while ( false !== ( $file = readdir( $dh ) ) ) {
312
			if ( $file == '.' || $file == '..' ) continue;
313
			$files[] = "$path/$file";
314
		}
315
316
		closedir( $dh );
317
		sort( $files );
318
		return $files;
319
	}
320
}
321