clrghouz/app/Classes/FTNMessage.php
2019-05-11 11:17:56 +10:00

403 lines
10 KiB
PHP

<?php
namespace App\Classes;
use App\Exceptions\InvalidFidoPacketException;
class FTNMessage extends FTN
{
private $_src = NULL;
private $_dst = NULL;
private $flags = NULL;
private $cost = 0;
private $from = NULL; // FTS-0001.016 From Name: upto 36 chars null terminated
private $to = NULL; // FTS-0001.016 To Name: upto 36 chars null terminated
private $subject = NULL; // FTS-0001.016 Subject: upto 72 chars null terminated
private $date = NULL; // FTS-0001.016 Date: upto 20 chars null terminated
private $message = NULL; // The actual message content
private $echoarea = NULL; // FTS-0004.001
private $intl = NULL;
private $msgid = NULL;
private $reply = NULL; // Message thread reply source
private $origin = NULL; // FTS-0004.001
private $kludge = []; // Hold kludge items
private $path = []; // FTS-0004.001
private $seenby = []; // FTS-0004.001
private $via = [];
private $_other = [];
private $unknown = [];
private $fqfa = NULL; // Fully qualified fidonet source where packet originated
private $fqda = NULL; // Fully qualified fidonet destination address (Netmail)
// Single value kludge items
private $_kludge = [
'chrs' => 'CHRS: ',
'charset' => 'CHARSET: ',
'codepage' => 'CODEPAGE: ',
'pid' => 'PID: ',
'tid' => 'TID: ',
];
// Flags for messages
const FLAG_PRIVATE = 0b1;
const FLAG_CRASH = 0b10;
const FLAG_RECD = 0b100;
const FLAG_SENT = 0b1000;
const FLAG_FILEATTACH = 0b10000;
const FLAG_INTRANSIT = 0b100000;
const FLAG_ORPHAN = 0b1000000;
const FLAG_KILLSENT = 0b10000000;
const FLAG_LOCAL = 0b100000000;
const FLAG_HOLD = 0b1000000000;
const FLAG_UNUSED_10 = 0b10000000000;
const FLAG_FREQ = 0b100000000000;
const FLAG_RETRECEIPT = 0b1000000000000;
const FLAG_ISRETRECEIPT = 0b10000000000000;
const FLAG_AUDITREQ = 0b100000000000000;
const FLAG_FILEUPDATEREQ = 0b1000000000000000;
// FTS-0001.016 Message header 12 bytes
// node, net, flags, cost
private $struct = [
'onode'=>[0x00,'v',2],
'dnode'=>[0x02,'v',2],
'onet'=>[0x04,'v',2],
'dnet'=>[0x06,'v',2],
'flags'=>[0x08,'v',2],
'cost'=>[0x0a,'v',2],
];
public function __construct(string $header=NULL)
{
// Initialise vars
$this->kludge = collect(); // The message kludge lines
$this->path = collect(); // The message PATH lines
$this->seenby = collect(); // The message SEEN-BY lines
$this->via = collect(); // The path the message has gone using Via lines
$this->_other = collect(); // Temporarily hold attributes we dont process yet.
$this->unknown = collect(); // Temporarily hold attributes we have no logic for.
if ($header)
$this->parseheader($header);
}
public function __get($k)
{
switch ($k)
{
case 'fz': return $this->znfp($this->fqfa,'z');
case 'fn': return $this->znfp($this->fqfa,'n');
case 'ff': return $this->znfp($this->fqfa,'f');
case 'fp': return $this->znfp($this->fqfa,'p');
case 'tz': return $this->znfp($this->fqda,'z');
case 'tn': return $this->znfp($this->fqda,'n');
case 'tf': return $this->znfp($this->fqda,'f');
case 'tp': return $this->znfp($this->fqda,'p');
default:
return isset($this->{$k}) ? $this->{$k} : NULL;
}
}
public function __set($k,$v)
{
switch ($k)
{
case 'fqfa':
case 'fqda':
$this->{$k} = $v;
if ($this->fqfa AND $this->fqda)
$this->intl = sprintf('%s %s',$this->fqda,$this->fqfa);
default:
$this->{$k} = $v;
}
}
/**
* Export an FTN message, ready for sending.
*
* @return string
*/
public function __toString(): string
{
$return = '';
$return .= pack(join('',collect($this->struct)->pluck(1)->toArray()),
$this->ff,
$this->tf,
$this->fn,
$this->tn,
$this->flags,
0 // @todo cost
);
// @todo use pack for this.
$return .= $this->date->format('d M y H:i:s')."\00";
$return .= $this->to."\00";
$return .= $this->from."\00";
$return .= $this->subject."\00";
$return .= $this->message."\00";
return $return;
}
public function description()
{
switch ($this->type())
{
case 'echomail': return sprintf('Echomail: '.$this->echoarea);
case 'netmail': return sprintf('Netmail: %s->%s',$this->fqfa,$this->fqda);
default:
return 'UNKNOWN';
}
}
/**
* Return an array of flag descriptions
*
* @return array
*
* http://ftsc.org/docs/fsc-0001.000
* AttributeWord bit meaning
--- --------------------
0 + Private
1 + s Crash
2 Recd
3 Sent
4 + FileAttached
5 InTransit
6 Orphan
7 KillSent
8 Local
9 s HoldForPickup
10 + unused
11 s FileRequest
12 + s ReturnReceiptRequest
13 + s IsReturnReceipt
14 + s AuditRequest
15 s FileUpdateReq
s - this bit is supported by SEAdog only
+ - this bit is not zeroed before packeting
*/
public function flags(int $flags): array
{
return [
'private'=>$this->isFlagSet($flags,self::FLAG_PRIVATE),
'crash'=>$this->isFlagSet($flags,self::FLAG_CRASH),
'recd'=>$this->isFlagSet($flags,self::FLAG_RECD),
'sent'=>$this->isFlagSet($flags,self::FLAG_SENT),
'killsent'=>$this->isFlagSet($flags,self::FLAG_KILLSENT),
'local'=>$this->isFlagSet($flags,self::FLAG_LOCAL),
];
}
private function isFlagSet($value,$flag)
{
return (($value & $flag) == $flag);
}
/**
* Parse the head of an FTN message
*
* @param string $header
*/
public function parseheader(string $header)
{
$result = unpack($this->unpackheader($this->struct),$header);
// For Echomail this is the packet src.
$this->psn = array_get($result,'onet');
$this->psf = array_get($result,'onode');
$this->_src = sprintf('%s/%s',
$this->psn,
$this->psf
);
// For Echomail this is the packet dst.
$this->pdn = array_get($result,'dnet');
$this->pdf = array_get($result,'dnode');
$this->_dst = sprintf('%s/%s',
$this->pdn,
$this->pdf
);
$this->flags = array_get($result,'flags');
$this->cost = array_get($result,'cost');
}
public function parsemessage(string $message)
{
// Remove DOS \n\r
$message = preg_replace("/\n\r/","\r",$message);
// Split out the <SOH> lines
$result = collect(explode("\01",$message))->filter();
foreach ($result as $k => $v)
{
// Search for \r - if that is the end of the line, then its a kludge
$x = strpos($v,"\r");
// If there are more characters, then put the kludge back into the result, so that we process it.
if ($x != strlen($v)-1)
{
/**
* Anything after the origin line is also kludge data.
*/
if ($y = strpos($v,"\r * Origin: "))
{
$this->message .= substr($v,$x+1,$y-$x-1);
$this->parseorigin(substr($v,$y));
$matches = [];
preg_match('/^.*\((.*)\)$/',$this->origin,$matches);
if (($this->type() == 'Netmail' AND array_get($matches,1) != $this->fqfa) OR ! array_get($matches,1))
throw new InvalidFidoPacketException(sprintf('Source address mismatch? [%s,%s]',$this->fqfa,array_get($matches,1)));
$this->fqfa = array_get($matches,1);
}
$v = substr($v,0,$x+1);
}
foreach ($this->_kludge as $a => $b) {
if ($t = $this->kludge($b,$v)) {
$this->kludge->put($a,$t);
break;
}
}
if ($t)
continue;
if ($t = $this->kludge('AREA:',$v))
$this->echoarea = $t;
// From point: <SOH>"FMPT <point number><CR>
elseif ($t = $this->kludge('FMPT ',$v))
$this->_other->push($t);
/*
* The INTL control paragraph shall be used to give information about
* the zone numbers of the original sender and the ultimate addressee
* of a message.
*
* <SOH>"INTL "<destination address>" "<origin address><CR>
*/
elseif ($t = $this->kludge('INTL ',$v))
{
$this->intl = $t;
list($this->fqda,$this->fqfa) = explode(' ',$t);
}
elseif ($t = $this->kludge('MSGID: ',$v))
$this->msgid = $t;
elseif ($t = $this->kludge('PATH: ',$v))
$this->path->push($t);
elseif ($t = $this->kludge('REPLY: ',$v))
$this->reply = $t;
// To Point: <SOH>TOPT <point number><CR>
elseif ($t = $this->kludge('TOPT ',$v))
$this->_other->push($t);
// Time Zone of the sender.
elseif ($t = $this->kludge('TZUTC: ',$v))
$this->tzutc= $t;
// <SOH>Via <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone] <Program Name> <Version> [Serial Number]<CR>
elseif ($t = $this->kludge('Via ',$v))
$this->via->push($t);
// We got a kludge line we dont know about
else {
$this->unknown->push(chop($v,"\r"));
//dd(['v'=>$v,'t'=>$t]);
}
}
}
/**
* Process the data after the ORIGIN
* There may be kludge lines after the origin - notably SEEN-BY
*
* @param string $message
*/
private function parseorigin(string $message)
{
// Split out each line
$result = collect(explode("\r",$message))->filter();
foreach ($result as $k => $v) {
foreach ($this->_kludge as $a => $b) {
if ($t = $this->kludge($b, $v)) {
$this->kludge->put($a, $t);
break;
}
}
if ($t = $this->kludge('SEEN-BY: ', $v))
$this->seenby->push($t);
elseif ($t = $this->kludge('PATH: ', $v))
$this->path->push($t);
elseif ($t = $this->kludge(' \* Origin: ',$v))
$this->origin = $t;
// We got unknown Kludge lines in the origin
else {
$this->unknown->push($v);
//dd(['v'=>$v,'t'=>$t,'message'=>$message]);
}
}
}
public function type()
{
if ($this->echoarea)
return 'echomail';
if ($this->intl)
return 'netmail';
return 'UNKNOWN';
}
private function znfp(string $data,string $key)
{
switch ($key) {
case 'z':
return substr($data,0,strpos($data,':'));
case 'n':
$x = strpos($data,':')+1;
return substr($data,$x,strpos($data,'/')-$x);
case 'f':
$x = strpos($data,'/')+1;
return substr($data,$x,strpos($data,'.') ?: strlen($data)-$x);
case 'p':
$x = strpos($data,'.');
return $x ? substr($data,$x+1) : 0;
default:
abort(500,'Unknown key: '.$key);
}
}
}