Support for ZIP archives
This commit is contained in:
@@ -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
92
app/Classes/File.php
Normal 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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user