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) { Log::info(sprintf('%s:Processing TIC file [%s]',self::LOGKEY,$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); $hex = basename($hex); if (! file_exists($filename)) throw new FileNotFoundException(sprintf('%s:File [%s] doesnt exist',self::LOGKEY,realpath($filename))); if (! is_writable($filename)) throw new UnableToWriteFile(sprintf('%s:File [%s] is not writable',self::LOGKEY,realpath($filename))); $f = fopen($filename,'rb'); if (! $f) { Log::error(sprintf('%s:! Unable to open file [%s] for writing',self::LOGKEY,$filename)); 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 (! Storage::disk('local')->exists($x=sprintf('%s/%s-%s',config('app.fido'),$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($x=Storage::disk('local')->path($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('%08x',$fo->crc) !== ($y=hash_file('crc32b',$x))) throw new \Exception(sprintf('TIC file CRC [%08x] 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 isNodelist(): bool { return (($this->fo->nodelist_filearea_id === $this->fo->filearea->domain->filearea_id) && (preg_match(str_replace(['.','?'],['\.','.'],'#^'.$this->fo->filearea->domain->nodelist_filename.'$#i'),$this->fo->file))); } }