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(); |
|
|
|
|
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 ); |
|
|
|
|
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 ); |
|
|
|
|
84
|
|
|
if ( !empty( $out ) ) |
85
|
|
|
$checksum = trim( array_shift( explode( ' ', array_pop( $out ) ) ) ); |
|
|
|
|
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; |
|
|
|
|
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; |
|
|
|
|
112
|
|
|
default: |
113
|
|
|
return false; |
114
|
|
|
break; |
|
|
|
|
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 ); |
|
|
|
|
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 ); |
|
|
|
|
245
|
|
|
if ( is_link( $dir.$entry ) ) |
246
|
|
|
continue; |
247
|
|
|
if ( $entry == '.' || $entry == '..' || $entry == '...' ) |
248
|
|
|
continue; |
249
|
|
|
if ( !$this->validate( $subdir.$entry ) ) |
|
|
|
|
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 ) ); |
|
|
|
|
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 ); |
|
|
|
|
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
|
|
|
|
If you suppress an error, we recommend checking for the error condition explicitly: