Added file areas and TIC processing

This commit is contained in:
Deon George
2022-11-01 22:24:36 +11:00
parent 702c5fb4f2
commit 029a8a9d73
20 changed files with 908 additions and 35 deletions

227
app/Classes/FTN/Tic.php Normal file
View File

@@ -0,0 +1,227 @@
<?php
namespace App\Classes\FTN;
use Carbon\Carbon;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnableToWriteFile;
use App\Classes\FTN as FTNBase;
use App\Models\{Address, File, Filearea, Setup};
use App\Traits\EncodeUTF8;
/**
* Class TIC
*
* @package App\Classes
*/
class Tic extends FTNBase
{
use EncodeUTF8;
private const LOGKEY = 'FM-';
private const cast_utf8 = [
];
// Single value kludge items and whether they are required
// http://ftsc.org/docs/fts-5006.001
private array $_kludge = [
'AREA' => TRUE,
'areadesc' => FALSE,
'ORIGIN' => TRUE,
'FROM' => TRUE,
'to' => FALSE,
'FILE' => TRUE, // 8.3 DOS format
'lfile' => FALSE, // alias fullname
'fullname' => FALSE,
'size' => FALSE,
'date' => FALSE, // File creation date
'desc' => FALSE, // One line description of file
'ldesc' => FALSE, // Can have multiple
'created' => FALSE,
'magic' => FALSE,
'replaces' => FALSE, // ? and * are wildcards, as per DOS
'CRC' => TRUE, // crc-32
'PATH' => TRUE, // can have multiple: [FTN] [unix timestamp] [datetime human readable] [signature]
'SEENBY' => TRUE,
'pw' => FALSE, // Password
];
private File $file;
private Filearea $area;
private ?string $areadesc = NULL;
private ?string $pw = NULL;
private Address $origin; // Should be first address in Path
private Address $from; // Should be last address in Path
private Address $to; // Should be me
public function __construct(private string $filename) {
$fo = new File;
$fo->kludges = collect();
$fo->set_path = collect();
$fo->set_seenby = collect();
$fo->rogue_path = collect();
$fo->rogue_seenby = collect();
list($hex,$name) = explode('-',$filename);
$ticfullpath = $this->fullpath($filename);
if (! file_exists($ticfullpath))
throw new FileNotFoundException(sprintf('%s:File [%s] doesnt exist',self::LOGKEY,realpath($ticfullpath)));
if (! is_writable($ticfullpath))
throw new UnableToWriteFile(sprintf('%s:File [%s] is not writable',self::LOGKEY,realpath($ticfullpath)));
Log::info(sprintf('Processing TIC file [%s]',$ticfullpath));
$f = fopen($ticfullpath,'rb');
if (! $f) {
Log::error(sprintf('%s:! Unable to open file [%s] for writing',self::LOGKEY,$ticfullpath));
return;
}
while (! feof($f)) {
$line = chop(fgets($f));
$matches = [];
if (! $line)
continue;
preg_match('/([a-zA-Z]+)\ (.*)/',$line,$matches);
if (in_array(strtolower($matches[1]),$this->_kludge)) {
switch ($k=strtolower($matches[1])) {
case 'area':
$this->{$k} = Filearea::singleOrNew(['name'=>strtoupper($matches[2])]);
break;
case 'origin':
case 'from':
case 'to':
$this->{$k} = Address::findFTN($matches[2]);
// @todo If $this->{$k} is null, we have discovered the system and it should be created
break;
case 'file':
if (! file_exists($x=$this->fullpath(sprintf('%s-%s',$hex,$matches[2]))))
throw new FileNotFoundException(sprintf('File not found? [%s]',$x));
$fo->{$k} = $matches[2];
$fo->fullname = $x;
break;
case 'areadesc':
case 'pw':
case 'created':
$this->{$k} = $matches[2];
break;
case 'lfile':
case 'size':
case 'desc':
case 'magic':
case 'replaces':
$fo->{$k} = $matches[2];
break;
case 'fullname':
$fo->lfile = $matches[2];
break;
case 'date':
$fo->datetime = Carbon::create($matches[2]);
break;
case 'ldesc':
$fo->{$k} .= $matches[2];
break;
case 'crc':
$fo->{$k} = hexdec($matches[2]);
break;
case 'path':
$x = [];
preg_match(sprintf('#^[Pp]ath (%s)\ ?([0-9]+)\ ?(.*)$#',Address::ftn_regex),$line,$x);
$ao = Address::findFTN($x[1]);
if (! $ao) {
$fo->rogue_path->push($matches[2]);
} else {
$fo->set_path->push(['address'=>$ao,'datetime'=>Carbon::createFromTimestamp($x[8]),'extra'=>$x[9]]);
}
break;
case 'seenby':
$ao = Address::findFTN($matches[2]);
if (! $ao) {
$fo->rogue_seenby->push($matches[2]);
} else {
$fo->set_seenby->push($ao->id);
}
break;
}
} else {
$fo->kludges->push($line);
}
}
fclose($f);
$f = fopen($fo->fullname,'rb');
$stat = fstat($f);
fclose($f);
// Validate Size
if ($fo->size !== ($y=$stat['size']))
throw new \Exception(sprintf('TIC file size [%d] doesnt match file [%s] (%d)',$fo->size,$fo->fullname,$y));
// Validate CRC
if (sprintf('%x',$fo->crc) !== ($y=hash_file('crc32b',$fo->fullname)))
throw new \Exception(sprintf('TIC file CRC [%x] doesnt match file [%s] (%s)',$fo->crc,$fo->fullname,$y));
// Validate Password
if ($this->pw !== ($y=$this->from->session('ticpass')))
throw new \Exception(sprintf('TIC file PASSWORD [%s] doesnt match system [%s] (%s)',$this->pw,$this->from->ftn,$y));
// Validate Sender is linked (and permitted to send)
if ($this->from->fileareas->search(function($item) { return $item->id === $this->area->id; }) === FALSE)
throw new \Exception(sprintf('Node [%s] is not subscribed to [%s]',$this->from->ftn,$this->area->name));
// If the filearea is to be autocreated, create it
if (! $this->area->exists) {
$this->area->description = $this->areadesc;
$this->area->active = TRUE;
$this->area->public = FALSE;
$this->area->notes = 'Autocreated';
$this->area->domain_id = $this->from->zone->domain_id;
$this->area->save();
}
$fo->filearea_id = $this->area->id;
$fo->fftn_id = $this->origin->id;
// If the file create time is blank, we'll take the files
if (! $fo->datetime)
$fo->datetime = Carbon::createFromTimestamp($stat['ctime']);
$fo->save();
$this->fo = $fo;
}
public function fullpath(string $file,string $prefix=NULL): string
{
return sprintf('storage/app/%s/%s',config('app.fido'),($prefix ? $prefix.'-' : '').$file);
}
}

View File

@@ -4,7 +4,7 @@ namespace App\Classes\File;
use Exception;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use League\Flysystem\UnreadableFileException;
use League\Flysystem\UnreadableFileEncountered;
/**
* A file we are sending or receiving
@@ -22,6 +22,7 @@ class Item
protected const IS_FILE = (1<<3);
protected const IS_FLO = (1<<4);
protected const IS_REQ = (1<<5);
protected const IS_TIC = (1<<6);
protected const I_RECV = (1<<6);
protected const I_SEND = (1<<7);
@@ -38,7 +39,7 @@ class Item
/**
* @throws FileNotFoundException
* @throws UnreadableFileException
* @throws UnreadableFileEncountered
* @throws Exception
*/
public function __construct($file,int $action)
@@ -54,7 +55,7 @@ class Item
throw new FileNotFoundException('Item doesnt exist: '.$file);
if (! is_readable($file))
throw new UnreadableFileException('Item cannot be read: '.$file);
throw new UnreadableFileEncountered('Item cannot be read: '.$file);
$this->file_name = $file;
$x = stat($file);
@@ -130,6 +131,9 @@ class Item
if (strcasecmp(substr($x,1),'req') == 0)
return self::IS_REQ;
if (strcasecmp(substr($x,1),'tic') == 0)
return self::IS_TIC;
for ($i=0;$i<count($ext);$i++)
if (! strncasecmp($x,'.'.$ext[$i],strlen($ext[$i])) && (preg_match('/^[0-9a-z]/',strtolower(substr($x,3,1)))))
return self::IS_ARC;

View File

@@ -10,7 +10,7 @@ use Symfony\Component\HttpFoundation\File\Exception\FileException;
use App\Classes\FTN\InvalidPacketException;
use App\Classes\FTN\Packet;
use App\Jobs\MessageProcess;
use App\Jobs\{MessageProcess,TicProcess};
use App\Models\Address;
/**
@@ -170,6 +170,14 @@ final class Receive extends Item
break;
case self::IS_TIC:
Log::info(sprintf('%s: - Processing tic file [%s]',self::LOGKEY,$this->file));
// Queue the tic to be processed later, in case the referenced file hasnt been received yet
TicProcess::dispatch($this->file);
break;
default:
Log::debug(sprintf('%s: - Leaving file [%s] in the inbound dir',self::LOGKEY,$this->file));
}

View File

@@ -6,7 +6,7 @@ use Exception;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileException;
use League\Flysystem\UnreadableFileEncountered;
use App\Models\Address;
@@ -135,7 +135,7 @@ final class Send extends Item
Log::error(sprintf('%s:! Item [%s] doesnt exist',self::LOGKEY,$file));
return;
} catch (UnreadableFileException) {
} catch (UnreadableFileEncountered) {
Log::error(sprintf('%s:! Item [%s] cannot be read',self::LOGKEY,$file));
return;
@@ -257,7 +257,7 @@ final class Send extends Item
*
* @param int $length
* @return string|null
* @throws UnreadableFileException
* @throws UnreadableFileEncountered
* @throws Exception
*/
public function read(int $length): ?string
@@ -276,7 +276,7 @@ final class Send extends Item
Log::debug(sprintf('%s: - Read [%d] bytes, file pos now [%d]',self::LOGKEY,strlen($data),$this->file_pos));
if ($data === FALSE)
throw new UnreadableFileException('Error reading file: '.$this->sending->file_name);
throw new UnreadableFileEncountered('Error reading file: '.$this->sending->file_name);
return $data;
}

View File

@@ -7,7 +7,7 @@ use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileException;
use League\Flysystem\UnreadableFileEncountered;
use App\Classes\Protocol as BaseProtocol;
use App\Classes\Sock\SocketClient;
@@ -511,7 +511,7 @@ final class Binkp extends BaseProtocol
try {
$data = $this->send->read(self::BP_BLKSIZE);
} catch (UnreadableFileException) {
} catch (UnreadableFileEncountered) {
$this->send->close(FALSE);
$this->sessionClear(self::SE_SENDFILE);

View File

@@ -3,7 +3,6 @@
namespace App\Classes\Protocol;
use Illuminate\Support\Facades\Log;
use League\Flysystem\UnreadableFileException;
use App\Classes\Protocol;
use App\Classes\Protocol\Zmodem as ZmodemClass;