Skip to content

Commit 9b96314

Browse files
committed
Cleanups and improvements to output on snipeit:restore command
1 parent ff1297c commit 9b96314

File tree

1 file changed

+99
-52
lines changed

1 file changed

+99
-52
lines changed

app/Console/Commands/RestoreFromBackup.php

Lines changed: 99 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Console\Command;
66
use ZipArchive;
77
use Illuminate\Support\Facades\Log;
8+
use enshrined\svgSanitize\Sanitizer;
89

910
class SQLStreamer {
1011
private $input;
@@ -242,9 +243,10 @@ public function handle()
242243

243244
$private_dirs = [
244245
'storage/private_uploads/accessories',
245-
'storage/private_uploads/assetmodels',
246-
'storage/private_uploads/maintenances',
247-
'storage/private_uploads/models',
246+
'storage/private_uploads/assetmodels' => 'storage/private_uploads/models', //this was changed from assetmodels => models Aug 10 2025
247+
'storage/private_uploads/asset_maintenances' => 'storage/private_uploads/maintenances', //this was changed from asset_maintenances => maintenances Aug 10 2025
248+
'storage/private_uploads/maintenances', //but let 'maintenances' take precedence
249+
'storage/private_uploads/models', //and let 'models' take precedence
248250
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
249251
'storage/private_uploads/audits',
250252
'storage/private_uploads/components',
@@ -262,7 +264,7 @@ public function handle()
262264
];
263265
$public_dirs = [
264266
'public/uploads/accessories',
265-
'public/uploads/assetmodels',
267+
// 'public/uploads/assetmodels' => 'public/uploads/models', //according to git, this was _never_ a thing... (see below)
266268
'public/uploads/maintenances',
267269
'public/uploads/assets', // these are asset _pictures_, not asset files
268270
'public/uploads/avatars',
@@ -273,7 +275,7 @@ public function handle()
273275
'public/uploads/departments',
274276
'public/uploads/locations',
275277
'public/uploads/manufacturers',
276-
'public/uploads/models',
278+
'public/uploads/models', // ...it's been this way for 9 years (as of late 2025)
277279
'public/uploads/suppliers',
278280
];
279281

@@ -286,15 +288,27 @@ public function handle()
286288
'public/uploads/favicon-uploaded.*',
287289
];
288290

289-
$all_files = $private_dirs + $public_dirs;
290-
291291
$sqlfiles = [];
292292
$sqlfile_indices = [];
293293

294294
$interesting_files = [];
295295
$boring_files = [];
296296
$unsafe_files = [];
297297

298+
$good_extensions = config('filesystems.allowed_upload_extensions_array');
299+
300+
$private_extensions = array_merge($good_extensions, ["csv", "key"]); //add csv, and 'key'
301+
$public_extensions = array_diff($good_extensions, ["xml"]); //remove xml
302+
303+
$sanitizer = new Sanitizer();
304+
305+
/**
306+
* TODO: I _hate_ the "continue 3" thing we keep doing here
307+
* I think a better approach might be to have the "each file" stuff be in a method on this class, and the
308+
* boring_files and interesting_files be properties on it that we fill out. Then, in that method, we could
309+
* just do a 'return' once the file is actually handled (yay or nay). We could also start to break out some of
310+
* the _other_ things that we do into their own methods too? But I don't care about that as much.
311+
*/
298312
for ($i = 0; $i < $za->numFiles; $i++) {
299313
$stat_results = $za->statIndex($i);
300314
// echo "index: $i\n";
@@ -309,7 +323,7 @@ public function handle()
309323
// skip macOS resource fork files (?!?!?!)
310324
if (strpos($raw_path, '__MACOSX') !== false && strpos($raw_path, '._') !== false) {
311325
//print "SKIPPING macOS Resource fork file: $raw_path\n";
312-
$boring_files[] = $raw_path;
326+
// $boring_files[] = $raw_path; //stop adding this to the boring files list; it's just confusing
313327
continue;
314328
}
315329
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
@@ -318,44 +332,70 @@ public function handle()
318332
$sqlfile_indices[] = $i;
319333
continue;
320334
}
321-
322-
foreach (array_merge($private_dirs, $public_dirs) as $dir) {
323-
$last_pos = strrpos($raw_path, $dir . '/');
324-
if ($last_pos !== false) {
325-
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
326-
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
327-
$interesting_files[$raw_path] = ['dest' => $dir, 'index' => $i];
328-
continue 2;
329-
if ($last_pos + strlen($dir) + 1 == strlen($raw_path)) {
330-
// we don't care about that; we just want files with the appropriate prefix
331-
//print("FOUND THE EXACT DIRECTORY: $dir AT: $raw_path!!!\n");
335+
if ($raw_path[-1] == '/') {
336+
//last character is '/' - this is a directory, and we don't need it, and we don't need to warn about it
337+
continue;
338+
}
339+
if (in_array(basename($raw_path), [".gitkeep", ".gitignore", ".DS_Store"])) {
340+
//skip these boring files silently without reporting on them; they're stupid
341+
continue;
342+
}
343+
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
344+
345+
foreach (['public' => $public_dirs, 'private' => $private_dirs] as $purpose => $dirs) {
346+
$allowed_extensions = match ($purpose) {
347+
'public' => $public_extensions,
348+
'private' => $private_extensions,
349+
};
350+
foreach ($dirs as $dir => $destdir) {
351+
if (is_int($dir)) {
352+
$dir = $destdir;
353+
}
354+
$last_pos = strrpos($raw_path, $dir . '/');
355+
if ($last_pos !== false) {
356+
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
357+
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
358+
//the CSV bit, below, is because we store CSV files as "blahcsv" - without an extension
359+
if (!in_array($extension, $allowed_extensions) && !($dir == "storage/private_uploads/imports" && substr($raw_path, -3) == "csv" && $extension == "")) {
360+
$unsafe_files[] = $raw_path;
361+
Log::debug($raw_path . ' from directory ' . $dir . ' is being skipped');
362+
} else {
363+
if ($dir != $destdir) {
364+
Log::debug("Getting ready to save file $raw_path to new directory $destdir");
365+
}
366+
$interesting_files[$raw_path] = ['dest' => $destdir, 'index' => $i];
367+
}
368+
continue 3;
332369
}
333370
}
334371
}
335372

336-
$good_extensions = config('filesystems.allowed_upload_extensions_array');
337-
338-
foreach (array_merge($private_files, $public_files) as $file) {
339-
$has_wildcard = (strpos($file, '*') !== false);
340-
if ($has_wildcard) {
341-
$file = substr($file, 0, -1); //trim last character (which should be the wildcard)
342-
}
343-
$last_pos = strrpos($raw_path, $file); // no trailing slash!
344-
if ($last_pos !== false) {
345-
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
346-
if (!in_array($extension, $good_extensions)) {
347-
// gathering potentially unsafe files here to return at exit
348-
$unsafe_files[] = $raw_path;
349-
Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
350-
$boring_files[] = $raw_path;
351-
continue 2;
373+
foreach (['public' => $public_files, 'private' => $private_files] as $purpose => $files) {
374+
$allowed_extensions = match ($purpose) {
375+
'public' => $public_extensions,
376+
'private' => $private_extensions,
377+
};
378+
foreach ($files as $file) {
379+
$has_wildcard = (strpos($file, '*') !== false);
380+
if ($has_wildcard) {
381+
$file = substr($file, 0, -1); //trim last character (which should be the wildcard)
352382
}
353-
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
354-
//no wildcards found in $file, process 'normally'
355-
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { //again, no trailing slash. or this is a wildcard and we just take it.
356-
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
357-
$interesting_files[$raw_path] = ['dest' => dirname($file), 'index' => $i];
358-
continue 2;
383+
$last_pos = strrpos($raw_path, $file); // no trailing slash!
384+
if ($last_pos !== false) {
385+
if (!in_array($extension, $allowed_extensions)) {
386+
// gathering potentially unsafe files here to return at exit
387+
$unsafe_files[] = $raw_path;
388+
Log::debug('Potentially unsafe file ' . $raw_path . ' is being skipped');
389+
$boring_files[] = $raw_path;
390+
continue 3;
391+
}
392+
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
393+
//no wildcards found in $file, process 'normally'
394+
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { //again, no trailing slash. or this is a wildcard and we just take it.
395+
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
396+
$interesting_files[$raw_path] = ['dest' => dirname($file), 'index' => $i];
397+
continue 3;
398+
}
359399
}
360400
}
361401
}
@@ -492,18 +532,25 @@ public function handle()
492532
}
493533
foreach ($interesting_files as $pretty_file_name => $file_details) {
494534
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
495-
$fp = $za->getStream($ugly_file_name);
496-
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
497-
if (!is_dir($file_details['dest'])) {
498-
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
499-
}
500-
$migrated_file = fopen($file_details['dest'].'/'.basename($pretty_file_name), 'w');
501-
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
502-
fwrite($migrated_file, $buffer);
535+
$migrated_file_name = $file_details['dest'] . '/' . basename($pretty_file_name);
536+
if (strcasecmp(substr($pretty_file_name, -4), ".svg") === 0) {
537+
$svg_contents = $za->getFromIndex($file_details['index']);
538+
$cleaned_svg = $sanitizer->sanitize($svg_contents);
539+
file_put_contents($migrated_file_name, $cleaned_svg);
540+
} else {
541+
$fp = $za->getStream($ugly_file_name);
542+
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
543+
if (!is_dir($file_details['dest'])) {
544+
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
545+
}
546+
$migrated_file = fopen($migrated_file_name, 'w');
547+
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
548+
fwrite($migrated_file, $buffer);
549+
}
550+
fclose($migrated_file);
551+
fclose($fp);
552+
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
503553
}
504-
fclose($migrated_file);
505-
fclose($fp);
506-
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
507554
if ($bar) {
508555
$bar->advance();
509556
}

0 commit comments

Comments
 (0)