Support for ZIP archives

This commit is contained in:
Deon George
2022-11-14 00:29:55 +11:00
parent 702a4e4f37
commit 3ffb1c1fd6
9 changed files with 441 additions and 295 deletions

View File

@@ -113,23 +113,26 @@ class Packet extends FTNBase implements \Iterator, \Countable
}
/**
* Open a packet file
* Process a packet file
*
* @param File $file
* @param mixed $f
* @param string $name
* @param int $size
* @param System|null $system
* @param bool $use_cache
* @return Packet
* @throws InvalidPacketException
*/
public static function open(File $file,System $system=NULL,bool $use_cache=TRUE): self
{
Log::debug(sprintf('%s:+ Opening Packet [%s]',self::LOGKEY,$file));
$f = fopen($file,'r');
$fstat = fstat($f);
public static function process(mixed $f,string $name,int $size,System $system=NULL,bool $use_cache=TRUE): self
{
Log::debug(sprintf('%s:+ Opening Packet [%s] with size [%d]',self::LOGKEY,$name,$size));
$read_ptr = 0;
// PKT Header
$header = fread($f,self::HEADER_LEN);
$read_ptr += strlen($header);
// Could not read header
if (strlen($header) != self::HEADER_LEN)
@@ -142,10 +145,11 @@ class Packet extends FTNBase implements \Iterator, \Countable
$o = new self;
$o->use_cache = $use_cache;
$o->name = (string)$file;
$o->name = $name;
$o->header = unpack(self::unpackheader(self::v2header),$header);
$x = fread($f,2);
$read_ptr += strlen($x);
// End of Packet?
if (strlen($x) == 2 and $x == "\00\00")
@@ -171,6 +175,8 @@ class Packet extends FTNBase implements \Iterator, \Countable
$last = '';
while ($buf_ptr || (! feof($f) && ($readbuf=fread($f,self::BLOCKSIZE)))) {
$read_ptr += strlen($readbuf);
if (strlen($message) < self::PACKED_MSG_HEADER_LEN) {
$addchars = self::PACKED_MSG_HEADER_LEN-strlen($message);
$message .= substr($readbuf,$buf_ptr,$addchars);
@@ -204,7 +210,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
// In case our packet break is at the end of the buffer
$last = substr($readbuf,-2);
if ((str_contains($last,"\x00")) && ($fstat['size']-ftell($f) > 2)) {
if ((str_contains($last,"\x00")) && ($size-$read_ptr > 2)) {
$message .= substr($readbuf,$buf_ptr);
$buf_ptr = 0;
@@ -216,7 +222,7 @@ class Packet extends FTNBase implements \Iterator, \Countable
}
// See if we have found the end of the packet, if not read more.
if ($end === FALSE && (ftell($f) < $fstat['size'])) {
if ($end === FALSE && ($read_ptr < $size)) {
$message .= substr($readbuf,$buf_ptr);
$buf_ptr = 0;

92
app/Classes/File.php Normal file
View File

@@ -0,0 +1,92 @@
<?php
namespace App\Classes;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\File\File as FileBase;
class File extends FileBase implements \Iterator
{
private const LOGKEY = 'F--';
private int $counter = 0;
private bool $isArchive = FALSE;
private bool $canHandle = FALSE;
private \ZipArchive $z;
private array $zipfile = [];
public function __construct(mixed $path,bool $checkPath=true)
{
parent::__construct($path,$checkPath);
switch($x=$this->guessExtension()) {
case 'zip':
$this->canHandle = TRUE;
$this->isArchive = TRUE;
$this->z = new \ZipArchive;
$this->z->open($this->getRealPath());
break;
case 'bin':
if ($this->getExtension() == 'pkt' || ($path instanceof UploadedFile && $path->getClientOriginalExtension() == 'pkt')) {
$this->canHandle = TRUE;
break;
};
default:
Log::alert(sprintf('%s:? Unknown file received: %s',self::LOGKEY,$x));
}
}
/* ITERATOR */
public function current()
{
if ($this->isArchive) {
$this->zipfile = $this->z->statIndex($this->counter,\ZipArchive::FL_UNCHANGED);
$f = $this->z->getStream($this->zipfile['name']);
if (! $f)
throw new \Exception(sprintf('%s:Failed getting ZipArchive::stream (%s)',self::LOGKEY,$this->z->getStatusString()));
return $f;
} else {
return fopen($this->getRealPath(),'r+');
}
}
public function next()
{
$this->counter++;
}
public function key()
{
return $this->counter;
}
public function valid()
{
// If we have a pkt file, then counter can only be 1.
return $this->canHandle && (($this->isArchive && ($this->counter < $this->z->numFiles)) || $this->counter === 0);
}
public function rewind()
{
$this->counter = 0;
}
/* METHODS */
public function itemName(): string
{
return ($this->isArchive && $this->valid()) ? Arr::get(stream_get_meta_data($this->current()),'uri') : $this->getFilename();
}
public function itemSize(): int
{
return $this->isArchive ? Arr::get($this->zipfile,'size') : $this->getSize();
}
}

View File

@@ -3,13 +3,13 @@
namespace App\Classes\File;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use App\Classes\FTN\InvalidPacketException;
use App\Classes\FTN\Packet;
use App\Classes\File;
use App\Classes\FTN\{InvalidPacketException,Packet};
use App\Jobs\{MessageProcess,TicProcess};
use App\Models\Address;
@@ -54,7 +54,7 @@ final class Receive extends Item
case 'mtime':
case 'name':
case 'size':
return $this->receiving ? $this->receiving->{'file_'.$key} : NULL;
return $this->receiving?->{'file_'.$key};
case 'name_size_time':
return sprintf('%s %lu %lu',$this->name,$this->size,$this->mtime);
@@ -108,64 +108,67 @@ final class Receive extends Item
// If we received a packet, we'll dispatch a job to process it
if (! $this->receiving->incomplete)
switch ($this->receiving->file_type) {
case self::IS_ARC:
case self::IS_PKT:
Log::info(sprintf('%s: - Processing mail packet [%s]',self::LOGKEY,$this->file));
Log::info(sprintf('%s: - Processing mail %s [%s]',self::LOGKEY,$this->receiving->file_type === self::IS_PKT ? 'PACKET' : 'ARCHIVE',$this->file));
try {
$po = Packet::open(new File($this->file),$this->ao->system);
$f = new File($this->file);
foreach ($f as $packet) {
$po = Packet::process($packet,Arr::get(stream_get_meta_data($packet),'uri'),$f->itemSize(),$this->ao->system);
// Check the messages are from the uplink
if ($this->ao->system->addresses->search(function($item) use ($po) { return $item->id == $po->fftn_o->id; }) === FALSE) {
Log::error(sprintf('%s: ! Packet [%s] is not from this link? [%d]',self::LOGKEY,$po->fftn_o->ftn,$this->ao->system_id));
break;
}
// Check the packet password
if ($this->ao->session('pktpass') != $po->password) {
Log::error(sprintf('%s: ! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$po->password));
// @todo Generate message to system advising invalid password - that message should be sent without a packet password!
break;
}
Log::info(sprintf('%s: - Packet has [%d] messages',self::LOGKEY,$po->count()));
// Queue messages if there are too many in the packet.
if ($queue = ($po->count() > config('app.queue_msgs')))
Log::info(sprintf('%s: - Messages will be sent to the queue for processing',self::LOGKEY));
$error = FALSE;
foreach ($po as $msg) {
Log::info(sprintf('%s: - Mail from [%s] to [%s]',self::LOGKEY,$msg->fftn,$msg->tftn));
// @todo Quick check that the packet should be processed by us.
// @todo validate that the packet's zone is in the domain.
try {
// Dispatch job.
if ($queue)
MessageProcess::dispatch($msg);
else
MessageProcess::dispatchSync($msg);
} catch (Exception $e) {
Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
$error = TRUE;
}
}
if ($po->errors->count() || $error) {
Log::info(sprintf('%s: - Not deleting packet [%s], as it has validation errors',self::LOGKEY,$this->file));
// If we want to keep the packet, we could do that logic here
} elseif (! config('app.packet_keep')) {
Log::debug(sprintf('%s: - Deleting processed packet [%s]',self::LOGKEY,$this->file));
unlink($this->file);
}
}
} catch (InvalidPacketException $e) {
Log::error(sprintf('%s: - Not deleting packet [%s], as it generated an exception',self::LOGKEY,$this->file));
break;
}
// Check the messages are from the uplink
if ($this->ao->system->addresses->search(function($item) use ($po) { return $item->id == $po->fftn_o->id; }) === FALSE) {
Log::error(sprintf('%s: ! Packet [%s] is not from this link? [%d]',self::LOGKEY,$po->fftn_o->ftn,$this->ao->system_id));
break;
}
// Check the packet password
if ($this->ao->session('pktpass') != $po->password) {
Log::error(sprintf('%s: ! Packet from [%s] with password [%s] is invalid.',self::LOGKEY,$this->ao->ftn,$po->password));
// @todo Generate message to system advising invalid password - that message should be sent without a packet password!
break;
}
Log::info(sprintf('%s: - Packet has [%d] messages',self::LOGKEY,$po->count()));
// Queue messages if there are too many in the packet.
if ($queue = ($po->count() > config('app.queue_msgs')))
Log::info(sprintf('%s: - Messages will be sent to the queue for processing',self::LOGKEY));
$error = FALSE;
foreach ($po as $msg) {
Log::info(sprintf('%s: - Mail from [%s] to [%s]',self::LOGKEY,$msg->fftn,$msg->tftn));
// @todo Quick check that the packet should be processed by us.
// @todo validate that the packet's zone is in the domain.
try {
// Dispatch job.
if ($queue)
MessageProcess::dispatch($msg);
else
MessageProcess::dispatchSync($msg);
} catch (Exception $e) {
Log::error(sprintf('%s:! Got error dispatching message [%s] (%d:%s-%s).',self::LOGKEY,$msg->msgid,$e->getLine(),$e->getFile(),$e->getMessage()));
$error = TRUE;
}
}
if ($po->errors->count() || $error) {
Log::info(sprintf('%s: - Not deleting packet [%s], as it has validation errors',self::LOGKEY,$this->file));
// If we want to keep the packet, we could do that logic here
} elseif (! config('app.packet_keep')) {
Log::debug(sprintf('%s: - Deleting processed packet [%s]',self::LOGKEY,$this->file));
unlink($this->file);
}
break;